【iOS锁_@synchronized源码浅析】

news2024/12/22 22:42:48

文章目录

  • 前言
  • @synchronized介绍
    • 加锁实例
    • @synchronized实现
    • objc_sync_enter 和 objc_sync_exit
  • objc_sync_enter
  • obj存在
    • SyncList的结构
    • `SyncList`和`SyncData`的关系
    • id2data函数的实现
        • 1. 使用快速缓存
        • 2. 获取该线程下的SyncCache
        • 3. 全局哈希表查找
        • 4. 生成新数据并写入缓存
  • 总结

前言

继续iOS锁的学习。在这里深入的学习@synchronized的实现过程。

@synchronized介绍

@synchronized 结构所做的事情跟锁(lock)类似:它防止不同的线程同时执行同一段代码。但在某些情况下,相比于使用 NSLock 创建锁对象、加锁和解锁来说,@synchronized 用着更方便,可读性更高。但并不是在任意场景下都能使用@synchronized,且它的性能较低。

用法

@synchronized (obj) {}

官方文档该锁的用法:

使用@synchronized指令

@synchronized指令是在Objective-C代码中实时创建互斥锁的便捷方式。@synchronized指令会做任何其他互斥锁都会做的事情——它防止不同的线程同时获取相同的锁。然而,在这种情况下,您不必直接创建互斥量或锁定对象。相反,您只需使用任何Objective-C对象作为锁定令牌,如以下示例所示:

-void)myMethod:(id)anObj
{
@synchronized(anObj)
{
//大括号之间的一切都受到@synchronized指令的保护。
}
}

传递给@synchronized指令的对象是用于区分受保护块的唯一标识符

需要注意:

  • 如果在两个不同的线程中执行上述方法,为每个线程上的anObj参数传递不同的对象,每个线程都会锁定并继续处理,而不会被另一个线程阻止。
  • 如果在这两种情况下都传递相同的对象,其中一个线程将首先获得锁,另一个线程将阻塞,直到第一个线程完成关键部分。

作为预防措施,@synchronized块隐式地将异常处理程序添加到受保护的代码中。如果抛出异常,此处理程序会自动释放互斥体。这意味着,为了使用@synchronized指令,必须在代码中启用Objective-C异常处理。

加锁实例

  • 无锁:
// @synchronized
- (void)testSynchronized {
    // 模拟多窗口售票
    self.ticketCount = 20;//一共有 20 张车票,分为 4 个窗口售卖
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 5; i++) {
                [self saleTicket];
            }
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 5; i++) {
                [self saleTicket];
            }
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 3; i++) {
                [self saleTicket];
            }
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 10; i++) {
                [self saleTicket];
            }
        });
}
// 售票方法
- (void)saleTicket{
        if (self.ticketCount > 0) {
            self.ticketCount--;
            sleep(0.1);
            NSLog(@"当前余票还剩:%lu张",(unsigned long)self.ticketCount);
        }else{
            NSLog(@"当前车票已售罄");
        }
}

在无锁的情况下:数据在18张和14张的时候发生了冲突
请添加图片描述

  • 加锁:
// @synchronized
- (void)testSynchronized {
    // 模拟多窗口售票
    self.ticketCount = 20;//一共有 20 张车票,分为 4 个窗口售卖
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 5; i++) {
                [self saleTicket];
            }
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 5; i++) {
                [self saleTicket];
            }
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 3; i++) {
                [self saleTicket];
            }
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            for (int i = 0; i < 10; i++) {
                [self saleTicket];
            }
        });
}
// 售票方法
- (void)saleTicket{
    @synchronized (self) {
        if (self.ticketCount > 0) {
            self.ticketCount--;
            sleep(0.1);
            NSLog(@"当前余票还剩:%lu张",(unsigned long)self.ticketCount);
        }else{
            NSLog(@"当前车票已售罄");
        }
    }
}

请添加图片描述

@synchronized实现

打开Debug调试
在这里插入图片描述
上面的汇编代码可以看到,@synchronized block会变成 objc_sync_enter 和 objc_sync_exit 的成对儿调用。

那么就从这两个方法入手详细了解@synchronized

objc_sync_enter 和 objc_sync_exit

在objc843可编译源码查看方法。接口部分给了一部分注释

#include <objc/objc.h>


/**
 * 开始对“obj”进行加锁。
 *
 * Begin synchronizing on 'obj'.
 *
 *如果需要,分配与“obj”关联的递归pthread_mutex。
 * Allocates recursive pthread_mutex associated with 'obj' if needed.
 *
 *要开始加锁的对象。
 * @param obj The object to begin synchronizing on.
 * 
 * @return OBJC_SYNC_SUCCESS once lock is acquired.  
 */
OBJC_EXPORT int
objc_sync_enter(id _Nonnull obj)
    OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0);

/** 
 * End synchronizing on 'obj'.
 * 结束对“obj”的锁。
 *
 * obj要结束加锁的对象。
 * @param obj The object to end synchronizing on.
 * 
 * @return OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
 */
OBJC_EXPORT int
objc_sync_exit(id _Nonnull obj)
    OBJC_AVAILABLE(10.3, 2.0, 9.0, 1.0, 2.0);

enum {
    OBJC_SYNC_SUCCESS                 = 0,
    OBJC_SYNC_NOT_OWNING_THREAD_ERROR = -1
};


#endif // __OBJC_SYNC_H_

注释理解:

  • @synchronized 结构在工作时为传入的对象分配了一个锁
  • objc_sync_enter是同步开始的函数
  • objc_sync_exit是同步结束或者出现错误的函数

objc_sync_enter

请添加图片描述
从该方法的注释能看到,这个方法在给obj对象加锁的时候 分配的是recursive mutex。

  • 可以得出@synchronized是递归锁
  • 看看源码
// Begin synchronizing on 'obj'. 
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.
// 开始加锁
int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS; // 定义一个整型变量 result 并初始化为 OBJC_SYNC_SUCCESS,表示操作成功。

    if (obj) { //检查传入的 obj 参数是否非空。
        SyncData* data = id2data(obj, ACQUIRE); // 通过 id2data 函数获取 obj 对象关联的 SyncData 结构体数据
        ASSERT(data); // 断言确保获取到有效的同步数据。
        data->mutex.lock(); // 通过 data 结构体中的 mutex(互斥锁)成员进行加锁操作,确保只有一个线程能够进入临界区。
    } else { // 如果传入的 obj 参数为空。
        // @synchronized(nil) does nothing
        if (DebugNilSync) { // 检查是否启用了调试模式,用于调试 @synchronized(nil) 的情况。
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug"); // 向运行时库发送调试信息,提示使用者在 objc_sync_nil 上设置断点进行调试。
        }
        objc_sync_nil(); // 执行 objc_sync_nil 函数,该函数用于处理 @synchronized(nil) 的情况,实际上不执行任何操作。
    }

    return result; // 返回操作结果。
}
// 总体来说,这段代码的作用是获取对象关联的同步数据,然后对同步数据中的互斥锁进行加锁操作,确保只有一个线程能够进入临界区。如果传入的对象为空,则不执行任何操作
  • 该方法的作用是获取对象关联的同步数据,然后对同步数据中的互斥锁进行加锁操作,确保只有一个线程能够进入临界区。如果传入的对象为空,则不执行任何操作
  • 如果锁的对象obj不存在时分别会走objc_sync_nil()和不做任何操作
BREAKPOINT_FUNCTION(
    void objc_sync_nil(void)
);

这也是@synchronized作为递归锁但能防止死锁的原因所在:在不断递归的过程中如果对象不存在了就会停止递归从而防止死锁

obj存在

if (obj) { //检查传入的 obj 参数是否非空。
        SyncData* data = id2data(obj, ACQUIRE); // 通过 id2data 函数获取 obj 对象关联的 SyncData 结构体数据

(obj存在)会通过id2data方法生成一个SyncData对象

SyncData 结构体的设计用于管理 @synchronized 关键字的同步数据,包括记录对象、线程数量以及互斥锁等信息,以实现线程同步和保护临界区的功能。

typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData; // 指向下一个 SyncData 结构体的指针,用于构建一个链表结构,以管理多个对象的同步数据
    DisguisedPtr<objc_object> object; // 使用 DisguisedPtr 进行封装的 objc_object 类型的对象指针,表示与该同步数据相关联的对象。
    int32_t threadCount;  // number of THREADS using this block
    // 表示正在使用该同步数据的线程数量,用于记录进入临界区的线程数量。
    recursive_mutex_t mutex; // 一个递归互斥锁,用于实现线程同步和保护临界区代码。
} SyncData;


具体成员的解释如下:

  • nextData 指的是链表中下一个SyncData,通过链表将多个对象的同步数据连接起来,方便管理和查找。
  • object 指的是当前加锁的对象, DisguisedPtr 类型的对象指针,用于存储与该同步数据关联的对象的地址
  • threadCount 表示使用该对象进行加锁的线程数,每当有线程进入临界区时,threadCount 的值会增加,线程退出临界区时,threadCount 的值会减少。通过记录线程数量,可以确保临界区的正确性。
  • mutex 即对象所关联的锁,是一个递归互斥锁。

结构体的对齐方式通过 alignas(CacheLineSize) 进行指定,其中 CacheLineSize 表示缓存行的大小,用于优化内存对齐,避免伪共享等性能问题。

既然SyncData是链表中的某一个节点,保存了节点信息,那就看看SyncList的结构

SyncList的结构

  • SyncList 结构体的设计用于管理多个对象的同步数据,并提供了自旋锁来保护对 SyncList 的并发访问
  • 使用链表的原因可以用于存储和操作多个对象的同步信息。

struct SyncList {
    SyncData *data; // 指向 SyncData 结构体的指针,用于构建一个链表结构,管理多个对象的同步数据。
    spinlock_t lock; // 一个自旋锁(spinlock_t),用于保护对 SyncList 的操作,确保并发访问的正确性。

    // SyncList 的构造函数使用了 constexpr 修饰符,表示该构造函数可以在编译时求值,用于初始化 SyncList 结构体的实例。构造函数初始化了 data 成员为 nil(空指针),并将 lock 成员初始化为 fork_unsafe_lock(一个自旋锁的初始状态)。
    constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};

SyncListSyncData的关系

既然@synchronized能在任意地方(VC、View、Model等)使用,那么底层必然维护着一张全局的表(类似于weak表)。而从SyncListSyncData的结构可以证实系统确实在底层维护着一张哈希表,里面存储着SyncList结构的数据。SyncListSyncData的关系如下图所示:
请添加图片描述
SyncListSyncData 之间存在一种主从关系,即 SyncList 通过 data 指针管理多个 SyncData 结构体的链表。

具体来说,SyncList 结构体中的 data 成员是一个指针,指向第一个 SyncData 结构体的地址。通过这个指针,可以遍历整个链表,访问和管理每个 SyncData 结构体。

SyncData 结构体本身包含了一个指向下一个 SyncData 结构体的指针 nextData,以及其他的同步相关数据,如 objectthreadCountmutex。通过 nextData 指针,可以将多个 SyncData 结构体连接成一个链表。

因此,SyncList 结构体通过持有 SyncData 结构体的链表,实现了对多个对象的同步数据的管理和访问。通过操作 SyncList 中的 data 指针和 SyncData 中的 nextData 指针,可以进行链表的遍历、添加、删除等操作,以维护和操作同步数据的集合。

id2data函数的实现

id2data函数应该分成很多步骤实现,我们将 id2data函数内部实现根据他的宏定义划分各个板块挨个学习

  • id2data 函数主要用于根据给定的对象查找与之关联的同步数据SyncData)。它首先检查当前线程的缓存,然后再查找全局的同步数据列表,以获取与给定对象匹配的同步数据。

1. 使用快速缓存

// object 是一个类型为 id 的参数,表示待处理的对象。
// why 是一个枚举类型的参数,表示函数的使用目的
// enum usage { ACQUIRE, RELEASE, CHECK };
static SyncData* id2data(id object, enum usage why)
{
    
    spinlock_t *lockp = &LOCK_FOR_OBJ(object); //  // 从SyncList中通过object,获取lock
    SyncData **listp = &LIST_FOR_OBJ(object); //   // 从SyncData通过object获取data
    SyncData* result = NULL; // // 设置初始值

    // 如果支持直接线程键(SUPPORT_DIRECT_THREAD_KEYS 定义为真),则尝试从线程的快速缓存中查找与对象匹配的 SyncData
#if SUPPORT_DIRECT_THREAD_KEYS
    // Check per-thread single-entry fast cache for matching object
    // 检查每线程单项快速缓存中是否有匹配的对象
    bool fastCacheOccupied = NO;
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    if (data) {
        fastCacheOccupied = YES;

        if (data->object == object) {
            // Found a match in fast cache. 在快速缓存中找到匹配项。
            uintptr_t lockCount;

            result = data;
            lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
            if (result->threadCount <= 0  ||  lockCount <= 0) {
                _objc_fatal("id2data fastcache is buggy");
            }
// enum usage { ACQUIRE, RELEASE, CHECK };
            // why 是一个枚举类型的参数,表示函数的使用目的
            
            
            switch(why) {
            case ACQUIRE: {
                lockCount++;
                // 这里使用了 tls_get_direct 和 tls_set_direct 函数进行线程本地存储的读取和写入。
               //  如果在快速缓存中找到了匹配的 SyncData,则将结果赋值为找到的 SyncData,并根据 why 参数进行相应的操作。
               //  如果未在快速缓存中找到匹配项,则继续执行后续的逻辑。
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                break;
            }
            case RELEASE:
                lockCount--;
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                if (lockCount == 0) {
                    // remove from fast cache
                    tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
                    // atomic because may collide with concurrent ACQUIRE
                    OSAtomicDecrement32Barrier(&result->threadCount);
                }
                break;
            case CHECK:
                // do nothing
                break;
            }

            return result;
        }
    }
  • 这里有个重要的知识点——TLS:TLS全称为Thread Local Storage,在iOS中每个线程都拥有自己的TLS,负责保存本线程的一些变量, 且TLS无需锁保护
  • 快速缓存的含义为:定义两个变量SYNC_DATA_DIRECT_KEY/SYNC_COUNT_DIRECT_KEY,与tsl_get_direct/tls_set_direct配合可以从线程局部缓存中快速取得SyncCacheItem.data和SyncCacheItem.lockCount

如果在缓存中找到当前对象,就拿出当前被锁的次数lockCount,再根据传入参数类型(获取、释放、查看)对lockCount分别进行操作

(lockCount表示被锁的次数,递归锁可以多次进入)

  • 获取资源ACQUIRE:lockCount++并根据key值存入被锁次数
  • 释放资源RELEASE:lockCount++并根据key值存入被锁次数。如果次数变为0,此时锁也不复存在,需要从快速缓存移除并清空线程数threadCount
  • 查看资源check:不操作

2. 获取该线程下的SyncCache

因为步骤 1 是能直接找到线程的键进行快速缓存查找。endif 这个逻辑分支是找不到确切的线程标记只能进行所有的缓存遍历。

#endif 
static SyncData* id2data(id object, enum usage why)
{	
 ....
 
  /*2.  获取该线程下的SyncCache */
    
    // Check per-thread cache of already-owned locks for matching object 检查匹配对象的已拥有锁的每线程缓存。
    SyncCache *cache = fetch_cache(NO); // 检索当前线程的每个线程缓存对象 cache。
    if (cache) { // 检查缓存是否存在。
        unsigned int i;
        for (i = 0; i < cache->used; i++) {
            SyncCacheItem *item = &cache->list[i]; // 获取索引 i 处的缓存项。
            if (item->data->object != object) continue; // 如果缓存项中的对象与指定对象不匹配,则跳过下一次迭代。

            // Found a match.找到一个匹配对象
            result = item->data; // 将匹配的锁数据赋值给 result 变量。
            if (result->threadCount <= 0  ||  item->lockCount <= 0) {
                _objc_fatal("id2data cache is buggy"); // 检查锁数据是否有效,如果无效,则抛出错误。
            }
                // 根据why的值,执行下列操作。
            switch(why) {
            case ACQUIRE:
                item->lockCount++; // 增加锁计数。
                break;
            case RELEASE:
                item->lockCount--; // 减少锁计数
                if (item->lockCount == 0) {
                    //  // 如果计数达到零,则从每个线程缓存中移除该项,并对线程计数执行原子递减操作。
                    // remove from per-thread cache
                    cache->list[i] = cache->list[--cache->used]; // 从每个线程缓存中移除该项
                    // atomic because may collide with concurrent ACQUIRE
                    OSAtomicDecrement32Barrier(&result->threadCount); // 并对线程计数执行原子递减操作。
                }
                break;
            case CHECK:
                // do nothing 啥也不做
                break;
            }

            return result;
        }
    }

SyncCacheSyncCacheItem

typedef struct {
    SyncData *data;             //该缓存条目对应的SyncData
    unsigned int lockCount;     //该对象在该线程中被加锁的次数
} SyncCacheItem;

typedef struct SyncCache {
    unsigned int allocated;     //该缓存此时对应的缓存大小
    unsigned int used;          //该缓存此时对应的已使用缓存大小
    SyncCacheItem list[0];      //SyncCacheItem数组
} SyncCache;

sync:汉语同步。

  • SyncCacheItem用来记录某个SyncData在某个线程中被加锁的记录,一个SyncData可以被多个SyncCacheItem持有
  • SyncCache用来记录某个线程中所有SyncCacheItem,并且记录了缓存大小以及已使用缓存大小

3. 全局哈希表查找

快速、慢速流程都没找到缓存就会来到这步——在系统保存的哈希表进行链式查找

static SyncData* id2data(id object, enum usage why)
{	
 ....
 ....
/*3.  全局哈希表查找*/
    
    // 快速、慢速流程都没找到缓存就会来到这步——在系统保存的哈希表进行链式查找
    
    lockp->lock(); // lockp->lock();`是在查找过程前后进行了加锁操作 是对锁进行加锁操作,确保在访问同步数据时的线程安全性。

    {
        SyncData* p;
        SyncData* firstUnused = NULL; // 感觉是用来 寻找链表中未使用的SyncData并作标记
        // 使用一个循环遍历同步数据列表,查找与给定对象匹配的同步数据项。
        for (p = *listp; p != NULL; p = p->nextData) {
            if ( p->object == object ) {
                result = p; // 如果找到匹配项,将其赋值给 `result` 变量
                // atomic because may collide with concurrent RELEASE
                OSAtomicIncrement32Barrier(&result->threadCount); // 通过原子操作 `OSAtomicIncrement32Barrier` 增加该同步数据的线程计数(`threadCount`)。这是为了处理并发情况下的释放操作(`RELEASE`)可能与当前查找操作同时进行的情况。
                goto done;
            }
            if ( (firstUnused == NULL) && (p->threadCount == 0) )
                firstUnused = p;
        }
    
        // no SyncData currently associated with object 如果遍历过程中找不到与给定对象匹配的同步数据项
        if ( (why == RELEASE) || (why == CHECK) ) // 则判断当前的操作类型(`why`)。如果是释放操作(`RELEASE`)或检查操作(`CHECK`),则跳转到 `done` 标签处结束。
            goto done;
    
        // an unused one was found, use it
        if ( firstUnused != NULL ) { // 如果遍历过程中找到一个未被使用的同步数据项(
            result = firstUnused; // 则将其作为可用的同步数据项,并将其关联到给定对象
            result->object = (objc_object *)object;
             
            result->threadCount = 1; // 然后将线程计数设置为 1,并跳转到 `done` 标签处结束。
            goto done;
        }
    }

通过以上步骤,代码可以找到与给定对象关联的同步数据项,并进行相应的操作或返回

  • 如果找到匹配项,它会增加线程计数以表示当前线程正在使用该同步数据;
  • 如果找不到匹配项,根据操作类型(如释放操作)可能会进行相应的处理。最终,通过 result 变量返回找到的同步数据项或 NULL

4. 生成新数据并写入缓存

倘若前三步全均不符合 。即链表不存在——对象对于全部线程来说是第一次加锁)就会创建SyncData并存在result里。

//  /*4. 生成新数据并写入缓存*/
    posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData)); // 使用 posix_memalign 函数分配内存空间,并将其类型转换为 SyncData 结构体的指针。这样可以确保新的同步数据在内存中按照指定的对齐方式分配。
    result->object = (objc_object *)object; // 将给定对象关联到新的同步数据项。
    result->threadCount = 1; // 设置线程计数为 1。
    new (&result->mutex) recursive_mutex_t(fork_unsafe_lock); // 使用定位 new 的方式在 result->mutex 成员变量上构造 recursive_mutex_t 对象,并使用 fork_unsafe_lock 作为初始值。
    // new这一步和链表的初始化很相似。
    result->nextData = *listp; // 将新的同步数据项插入到同步数据列表的头部,更新 *listp 的值为新的同步数据项。
    *listp = result;
    
// 接下来,通过 done 标签进行清理和处理的步骤如下:
 done:
    lockp->unlock(); // 解锁 lockp,释放对锁的占用。
    if (result) { // 检查 result 是否存在。如果存在,则表示是新的获取操作(ACQUIRE)。进一步处理该情况。
        // Only new ACQUIRE should get here.
        // All RELEASE and CHECK and recursive ACQUIRE are 
        // handled by the per-thread caches above.
        
        if (why == RELEASE) { // 如果当前操作是释放操作(RELEASE)
            // Probably some thread is incorrectly exiting 🉑️能表示某个线程在其他线程持有该对象时错误地退出。
            // while the object is held by another thread.
            return nil;
        }
        // 如果当前操作不是获取操作(ACQUIRE),则表示 id2data 函数存在错误
        if (why != ACQUIRE) _objc_fatal("id2data is buggy");
        // 检查 result->object 是否与给定对象匹配。如果不匹配,表示 id2data 函数存在错误。
        if (result->object != object) _objc_fatal("id2data is buggy");

        
#if SUPPORT_DIRECT_THREAD_KEYS // 如果支持直接线程键(SUPPORT_DIRECT_THREAD_KEYS)
        if (!fastCacheOccupied) { // 并且之前没有占用快速缓存(fastCacheOccupied),
            // Save in fast thread cache
            tls_set_direct(SYNC_DATA_DIRECT_KEY, result); // 则将新的同步数据项保存在快速线程缓存中。
            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1); // 同时,设置线程计数为 1。
        } else 
#endif
    // 如果不满足上述条件,将新的同步数据项保存在线程缓存中。
        {
            // Save in thread cache
            if (!cache) cache = fetch_cache(YES); // 首先获取当前线程的缓存(cache)
            cache->list[cache->used].data = result; // ,并将新的同步数据项和线程计数保存到缓存列表中。
            cache->list[cache->used].lockCount = 1;
            cache->used++;
        }
    }

    return result; // 函数返回 result,即找到或生成的同步数据项。

到这一步 该函数的作用就结束了。发现该函数不能实现快慢速的缓存查找,都会最终生成一个新的同步对象存在线程的缓存里面。

总结

多次锁同一个对象会有什么后果吗?

  • 使用@synchronized多次对同一个对象进行锁定不会导致任何后果或问题。每次执行@synchronized块时,会检查对象是否已被锁定,如果是,则当前线程会等待直到锁被释放。一旦锁被释放,线程可以再次获得该锁并继续执行。
  • 多次锁定同一个对象可能会导致线程等待的时间增加,因为每次获取锁时都需要等待前一个锁释放。这可能会影响程序的性能。建议在确保必要性的情况下,尽量避免多次对同一个对象使用@synchronized锁定。如果可能,可以使用其他同步机制来替代,例如使用GCD的串行队列或使用NSLock类。

加锁对象不能为nil,否则加锁无效,不能保证线程安全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/570767.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

35从零开始学Java之析构方法又是咋回事?

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 在上一篇文章中&#xff0c;壹哥给大家详细地介绍了构造方法的使用、特点等内容。我们知道&#xff0…

ESP32 :项目的创建及项目架构解析

一、项目的创建 方式一&#xff1a;基于IDF示例创建 在ESP&#xff0d;IDF中有example示例库&#xff0c;以其中的一个示例为模板创建项目。 1、打开示例库 查看 - 命令面板&#xff08;也可以按住CtrlShiftP 或 F&#xff11;&#xff09; 输入 show examples projects 2…

大数据开发之Hive案例篇8-解析XML

文章目录 一. 问题描述二. 解决方案2.1 官方文档2.2 XML格式不规范 一. 问题描述 今天接到一个新需求&#xff0c;hive表里面有个字段存储的是XML类型数据 数据格式: <a><b>bb</b><c>cc</c> </a>二. 解决方案 2.1 官方文档 遇到不懂的…

PyTorch-Forecasting一个新的时间序列预测库

时间序列预测在金融、天气预报、销售预测和需求预测等各个领域发挥着至关重要的作用。PyTorch- forecasting是一个建立在PyTorch之上的开源Python包&#xff0c;专门用于简化和增强时间序列的工作。在本文中我们介绍PyTorch-Forecasting的特性和功能&#xff0c;并进行示例代码…

xhs小红薯【帖子】采集工具python爬虫抓取

一、xhs【帖子/笔记/视频】采集工具链接 &#xff08;请复制链接至浏览器&#xff0c;进行数据采集&#xff09; http://106.53.68.168:9920/xhs-keyword-spider 能爬取到的属性字段如图1 (点击右侧下拉按钮&#xff0c;可任选字段&#xff09; 图1属性字段 二、爬取规则 …

计算机毕业论文选题推荐|软件工程|系列九

文章目录 导文题目导文 计算机毕业论文选题推荐|软件工程 (***语言)==使用其他任何编程语言 例如:基于(***语言)门窗账务管理系统的设计与实现 得到:基于JAVA门窗账务管理系统的设计与实现 基于vue门窗账务管理系统的设计与实现 等等 题目 基于(***语言)学生在校信息管…

哪些pdf编辑软件值得下载?办公常备软件

PDF&#xff08;Portable Document Format&#xff09;是一种广泛用于电子文件传输的文档格式。为了更好的编辑和管理PDF文档&#xff0c;许多PDF编辑软件逐渐发展出来。本文将介绍PDF编辑软件的功能和使用方法。 使用PDF编辑软件可以提高我们的工作效率和文档管理能力。下面介…

【AI提示】ChatGPT提示工程课程(吴恩达OpenAI)迭代提示词笔记(中文chatgpt版)...

Iterative Prompt Develelopment 迭代提示词开发 在本课中&#xff0c;您将反复分析和优化您的提示&#xff0c;以从产品说明书生成营销文案。 设置 import openai import osfrom dotenv import load_dotenv, find_dotenv _ load_dotenv(find_dotenv()) # read local .env fil…

漫画管理工具Kapowarr

之前老苏写过不少漫画相关的软件&#xff0c;Mango、Kavita、Komga等等&#xff0c;但和今天要介绍的 Kapowarr 不太一样&#xff0c;如果你之前用过 Radarr、Sonarr 等 *arr 系列软件&#xff0c;应该是很容易上手的 什么是 Kapowarr &#xff1f; Kapowarr&#xff08;以前的…

基于Android studio二手车交易系统app

客户端&#xff1a; 用户注册&#xff1a;通过输入用户名&#xff0c;密码&#xff0c;所在地&#xff0c;联系地址以及电话和电子邮件等信息进行用户信息的注册。 二手车查看&#xff1a;用户注册登录系统后&#xff0c;可以查看二手车的基本信息&#xff0c;通过二手车的品牌…

【使用教程】NIMC2000控制器EtherCAT通讯下SDO位置清零

NIMC2000控制器是一种高性能的运动控制器&#xff0c;可通过EtherCAT通讯进行控制。在使用过程中&#xff0c;有时需要将位置清零&#xff0c;这可以通过SDO命令实现。 首先&#xff0c;需要确保NIMC2000控制器已经通过EtherCAT连接到了主机。然后&#xff0c;使用SDO命令将位…

AI技术:智慧交通时代的道路识别(文末送书四本)

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 自动驾驶是当前最热门的技术之一&#xff0c;而道路识别则是自动驾驶系统中的重要一环。它需要自动驾驶车辆能够识别和解读道路标志、路面标线、交通信号灯等道路条件&#xff0c;及时准确地做出驾驶决策。接下来Dream将带…

医药行业除钾钠,物料液体钾钠分离,特殊溶剂钾的提取

Tulsimer T-42是特级强酸型离子交换树脂&#xff0c;氢 H/钠 Na阳离子交换树脂&#xff0c; 是一款有较的交换容量 ,并同时拥有物理及化学稳定品质。可应用于汽电共生发电厂冷凝水处理及超纯水系统中的混床, 去除水中的阳离子。 Tulsimer T-42其无裂纹特性和均匀的粒度&#x…

【源码分析】【netty】FastThreadLocal 为什么快?

写在前面 接下来几篇文章&#xff0c;我们来聊一聊 netty 相关的。这里作者想先从 FastThreadLocal 开始说&#xff0c;而不是可能大家更熟悉的 reactor 啊&#xff0c;责任链设计啊&#xff0c;ByteBuf 啊&#xff0c;池化啊等等。不过虽然说 FastThreadLocal 熟知程度不如其…

剑指offer 2--数组中重复的元素

数组中重复的数字_牛客题霸_牛客网 (nowcoder.com) 【排序法】思路和代码&#xff1a; 对数组进行排序。遍历排序后的数组&#xff0c;如果当前元素与下一个元素相等&#xff0c;则找到了重复数字&#xff0c;返回该数字。如果遍历完数组都没有找到重复数字&#xff0c;则返回-…

ChatGPT应用组队学习来了!

Datawhale学习 联合主办&#xff1a;Datawhale、百度文心 Datawhale联合百度文心&#xff0c;五月为大家带来AIGC应用专题&#xff1a;大模型从入门到应用&#xff0c;学习大纲如下&#xff08;文末整理了这次学习的所有资料&#xff09;&#xff1a; 参与学习 ▶ 活动时间&am…

量子力学专题:线性谐振子

任何体系在平衡位置附近的小振动&#xff0c;例如 分子振动、晶格振动、原子核表面振动以及辐射场的振动等往往都可以分解成 若干彼此独立的一维简谐振动简谐振动往往还作为复杂运动的初步近似 见理论力学专题&#xff08;小振动&#xff09; 双原子分子&#xff0c;两原子间的…

kubernetes02

pod pod生命周期 pod的状态 1.挂起pending:API server创建了pod资源对象已存入etcd中&#xff0c;但它尚未被调度完成&#xff0c;或者仍处于从仓库下载镜像的过程中 2.运行中running:pod已经被调度到某节点&#xff0c;并且所有容器都已经被kubelet创建完成 3.成功complet:…

物业设备管理系统

物业服务质量难以保证&#xff0c;工单处理慢&#xff0c;巡检记录不规范&#xff1b;物业设备设施管理混乱&#xff0c;维修保养成本高&#xff0c;风险隐患多&#xff1b;物业数据分散&#xff0c;难以统计分析&#xff0c;无法提供决策支持&#xff1b;每天需要检查和保养的…

Hadoop学习---8、Hadoop数据压缩

1、Hadoop数据压缩 1.1 概述 1、压缩的好处和坏处 &#xff08;1&#xff09;优点&#xff1a;减少磁盘IO、减少磁盘储存空间 &#xff08;2&#xff09;缺点&#xff1a;增加CPU开销 2、压缩原则 &#xff08;1&#xff09;运算密集型的Job&#xff0c;少用压缩 &#xff08…