这是我参与8月更文挑战的第25天,活动详情查看: 8月更文挑战
在上一篇文章iOS底层分析-锁(二)中我们介绍了sDataList的结构,接下来我们继续分析SyncData的存储逻辑及Synchronized的执行流程;
synchronized执行流程
第一次执行
我们给@synchronized打上断点,然后执行代码,看第一次代码如何执行:
我们发现,第一次执行@synchronized,代码直接执行此处代码;
根据注释,我们可以看到这段代码的逻辑:
- 创建一个新的
SyncData,并添加到listp中; - 第一次进来肯定有一个线程在操作,
threadCount设为1; - 初始化
mutex锁; result->nextData = *listp; *listp = result;说明在链表SyncList中采用头插法存放SyncData;
继续执行:
将数据存储起来;然后返回SyncData类型的result;
第二次执行
继续执行,需要注意的是,我们第二次调用@synchronized的时候,传入的参数是p2,跟第一次传入的p1是不同的对象
代码执行到:
引入第一次执行的时候,我们已经将p1对应的SyncData存储起来了,所以此处data有值;但是我们继续执行之后,发现代码又来到了:
又重复了第一次的步骤,这是为什么呢?这是因为,我们传入的p2跟之前的p1不是同一个对象,所以存放的SyncList也不同,所以会重新生成新的SyncData;
那么我们将p2改为p1,然后来看一下代码第二次执行的逻辑:
因为前后两次传入的对象相同,所以进入判断;
- 如果
why是ACQUIRE,lockCount加1; - 如果
why是RELEASE,lockCount减1; - 如果
lockCount为0,说明当前SyncData被当前线程解锁完成;OSAtomicDecrement32Barrier(&result->threadCount);对result->threadCount进行递减操作;
但是其他线程可能还有加锁,说明了@synchronized具备可多线程;
lockCount说明了@synchronized具备可递归性;表示在同一个线程下边被锁的次数;threadCount说明了@synchronized具备可多线程;表示被多少个线程加锁;
查找SyncData 方法解析
分析过SyncData的结构之后,我们继续分析在id2data方法内,SyncData是如何存储的?
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);在tls线程的局部存储(栈)区中查找是否存在SyncData;- 如果存在
SyncData,判断SyncData的object和当前传入的object是否一致;如果不一致咋不做操作,继续向下执行; - 如果查询出的
SyncData的object和当前传入的object一致,那么先给result赋值data,然后获取lockCount,根据传入的why的值是ACQUIRE或者RELEASE对lockCount进行加++或者--的操作,将lockCount保存;最后返回result;
实现流程与上面tls的流程非常相似;
总结
sDataLists是一张全局的哈希表,采用拉链法存放SyncData;sDataLists中的array存储的是SyncList,SyncList绑定了object;@synchronized封装了objc_sync_enter和objc_sync_exit,其封装的是一个递归锁recursive_mutex_t;SyncData支持两种存储原则:TLS和cache;- 第一次进入时,创建一个
SyncData,采用头插法存储进链表结构;threadCount标记为1; - 第二次进入时,判断是不是同一个对象;如果不是同一个对象,需要重新创建
SyncData,重新标记threadCount; - 如果是同一个对象,从
TLS中找到SyncData之后进行lockCount++; - 如果
TLS中找不到SyncData,重新创建一个SyncData,并进行threadCount++; - 如果
why为RELEASE时进行lockCount--和threadCount--
为什么synchronized具备可重入可递归多线程?
TLS保障threadCount标记对这个锁对象进行加锁的线程数量,lockCount++标记进来了多少次
synchronized的注意事项
- 锁的对象不要为空;
- 需要注意对象的生命周期;
- 推荐使用
self,除了生命周期的问题之外,传入同一个对象self时,我们只需要处理一条拉链;方便存储和释放; - 不要针对同一个对象,过多的加锁,导致拉链过长;
- 真机和模拟器因为
StripeCount不同的问题,导致性能相差巨大;