这是我参与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
不同的问题,导致性能相差巨大;