29.锁|@synchronized
一、 定位到 synchronized 相关源码
那么 synchronized 是怎么实现的呢?在 Xcode 中 @synchronized 是无法直接点进去的。我们尝试在 objc 源码中找线索。

这里找到了成对出现的 objc_sync_exit / objc_sync_enter。怀疑他就是 @synchronized 的底层实现。那么如何来验证一下呢?
这里我决定将 OC 代码还原为 C++ 代码来看看。

通过 clang -rewrite-objc main.m -o main.cpp 来进行还原。定位到 main 函数,其中的确成对出现了 objc_sync_exit / objc_sync_enter。
接下来我们可以放心的在 objc 源码中继续探索了。
二、 objc_sync_enter
这里会利用传入的
obj通过id2data获取一个SyncData结构体指针如果传入的
obj为空,什么都不做
三、 objc_sync_exit
四、 SyncData 的数据结构
nextData
SyncData 指针,可见是个
单向链表的结构这个结构是
synchronized可嵌套使用的关键点,后续会降到
object
传入的 obj
threadCount
有多少个线程在使用当前 block
mutex
递归锁
五、 id2data
5.1 找到 object 对应的数据结构

LOCK_FOR_OBJ找到对应的
锁 spinlock_t
LIST_FOR_OBJ找到对应的
SyncData的指针的指针
通过源码是从一个全剧静态哈希表 static StripedMap<SyncList> sDataLists 中获取的。
5.2 sDataLists 全剧静态哈希表
通过源码可以发现,貌似一个Hash表的结构。
容量大小 StripeCount
真机容量为 8,其他为 64
范型容器 array
这里容器的类型为 SyncList
容器元素 SyncList
哈希下标算法 indexForPointer
地址
addr右移4位得到A地址
addr右移9位得到BA异或B得到C用
C模容量得到下标保证了不会越界
思考一下:下标冲突了怎么办?
哈希冲突:拉链法(单向链表)
上面的图很好的描绘了哈希冲突时的情况
obj 计算出的下标冲突时会在链表中添加一个节点

5.3 查找TLS快速缓存(如果支持的话)
通过 线程局部存储(TLS:Thread Local Storage) 查找快速缓存。

5.4 fetch_cache 查找缓存
内部操作和 查找TLS快速缓存 基本一致。

5.5 未命中缓存
遍历链表查找

5.6 第一次进入 及 链表操作
这里通过头插法插入解决哈希冲突

到这里再回头看拉链法的那张图是否就更清楚了?
六、 SyncCacheItem
作为缓存的数据结构,其中的 lockCount 代表了,这个一个 Block 在 当前线程 锁了多少次(即嵌套锁)。
七、 总结
这里的设计十分巧妙,刚开始发现在真机下容器的大小只有 8 的时候还在想,难道最多只能有 8 个锁么?结果其中还存在单向链表的数据结构,同时使用单向链表还解决了哈希冲突。赞叹真的是很精妙的设计呀!👍
从源码中我们也可以注意到一些点:
锁的对象不要为空
锁的对象的生命周期要合适,不要中途就被释放了
这也是平时我们用
self的原因
尽量选择一样的 obj 来加锁,可以使
sDataLists结构更简单
参考
Last updated
Was this helpful?