@synchronized鎖原理的源碼分析
回顧
在上一篇博客中,已經(jīng)分析了第一次加鎖,data是空的,最后會創(chuàng)建SyncData并綁定到當(dāng)前線程上(一個線程只會綁定一個,并且綁定后不再改變),注意此時并沒有保存到線程對應(yīng)的緩存列表中。
源碼分析
單線程情況
那么現(xiàn)在去看看第二次加鎖,也就是斷點在44行時,進行跟蹤調(diào)試。

那么繼續(xù)單步調(diào)式進入源碼里面,斷點在id2data方法里面再進行 lldb的調(diào)式進行分析。

從圖中控制臺 lldb的調(diào)試結(jié)果來看,第二次進行加鎖時,data里面是有數(shù)據(jù)的了。那么繼續(xù)過斷點看看,緩存里面的情況:

此時緩存里面也有數(shù)據(jù)了,和上面打印的結(jié)果是一模一樣,都是data = 0x0000000100837d40。然后會繼續(xù)判斷,傳入對象是否是和緩存里面的一樣。
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");
}
如果是同一個對象,就會獲取lockCount,lockCount和threadCount是否小于等于 0進行判斷,如果小于 0則會報錯"id2data fastcache is buggy"

如果是ACQUIRE則會lockCount++,再進行鎖一次,說明當(dāng)前對象鎖了兩次,如下:

如何是RELEASE則會lockCount--,如果lockCount == 0,則會從線程空間緩存移除,這里也可以體現(xiàn)多線程的特性,從這句OSAtomicDecrement32Barrier(&result->threadCount)代碼可以看出,這是對線程進行釋放。

斷點繼續(xù),看看第三次加鎖的情況,如下:

因為是對同一個對象,進行了重復(fù)的操作,加鎖了3次,lockCount也是等于 3的,這也提現(xiàn)了拉鏈法,如下:

因為是同一個對象,每次加鎖,都會創(chuàng)建一個SyncData,就一直往后拉著,通過一個鏈表來存。
以上都是對一個對象進行重復(fù)的遞歸加鎖,那如果是不同對象呢?

不同對象也是類似的,就和上面那個結(jié)構(gòu)圖一樣,每個對象會創(chuàng)建一個拉鏈,同一個對象的就存在一個鏈表里面,這里就不再進行舉例了,感興趣的老鐵可以自行測試,源碼戳這里
多線程遞歸情況
那么現(xiàn)在通過多線程加鎖會怎么樣呢?測試代碼如下:

斷點從 52 行開始,進入到源碼里面跟蹤調(diào)式,這時候進入id2data方法,此時哈希表中的數(shù)據(jù)個數(shù)為2,也就是外層線程添加的兩個SyncData,如下圖:

繼續(xù)跟蹤代碼,從線程中獲取其綁定的SyncData,此時為NULL,因為是新的線程,還沒有加過鎖,所以綁定數(shù)據(jù)為空,fastCacheOccupied=NO

然后會繼續(xù)往下走,接著從緩存列表fetch_cache中獲取對應(yīng)的·SyncData·,也是·NULL·,這里的緩存列表也是和線程一一對應(yīng)的起來的,都是空。

繼續(xù)跟蹤流程,接著會進行線程threadCount++操作,如下圖:

這里會從listp中獲取對應(yīng)的數(shù)據(jù),在外層線程中,已經(jīng)添加了jp和jp2對應(yīng)的SyncData,這里是可以獲取到的,并且會對多線程操作,使得threadCount加1操作,此時對應(yīng)的線程數(shù)會從 1變成2,從上圖調(diào)試打印的結(jié)果可以很明顯的看到。
只要遇到新開線程,開始加鎖,tls和cache一定是空,肯定是listp中查找,或者是創(chuàng)建。一個線程中第一個添加的object一定會綁定到tls中,并且在當(dāng)前線程中不會改變。如果tls已經(jīng)完成設(shè)置,之后添加的SyncData都會添加到緩存列表中。
objc_sync_exit流程和這個相反,同樣會調(diào)用id2data方法,獲取SyncData,對lockCount和threadCount進行減操作。如果count等于0,則會從相應(yīng)的綁定關(guān)系和緩存列表中移除。
使用@synchronized的注意事項
參數(shù)不傳 nil
參數(shù)最好傳 self,方便存儲和釋放,如果是傳入一個這種
JPStudent *jp = [[JPStudent alloc]init]的,這個 jp是一個臨時的變量,如果有個多個這種,就會有多個拉鏈,耗費內(nèi)存和性能,只使用一個self 就只有一個拉鏈,雖然真機環(huán)境下,只有 8`個,但是已經(jīng)夠用了,即便不夠用,系統(tǒng)也會及時釋放回收的。

在之前的博客中進行的@synchronized測試,為什么模擬器下性能比真機差呢?就是上圖中 64的原因,模擬器拉鏈比較多,耗費內(nèi)存和性能。
總結(jié)
1: synchronized 哈希表 - 拉鏈法 存儲SyncData
2: sDataLists里面是一個 array存儲的是 SyncList,SyncList 里面是綁定的object
3: objc_sync_enter / exit 對稱 遞歸鎖
4: 兩種存儲 : TLS/ Cache
5: 第?次的時候 SyncData 才用頭插法 -鏈表 ,標(biāo)記 thracount = 1
6: 然后下次再進來會判斷是不是同?個對象
7: 是同一個對象TLS --> lockCount ++
8: 不是同一個的話TLS 找不到 就會去創(chuàng)建一個SyncData 則threadCount ++
9: objc_sync_exit 的話就是lockCount-- 和 threadCount--
@synchronized : 可重?遞歸 、多線程
1: 多線程是通過TLS 保障 threadCount 有多少條線程對這個鎖對象加鎖
2: 可重入遞歸是通過lockCount ++ 來表示進來鎖了多少次
補充TLS
線程局部存儲(Thread Local Storage,TLS): 是操作系統(tǒng)為線程單獨提供的私有空間,通常只有有限的容量。Linux系統(tǒng)下通常通過pthread庫中的 pthread_key_create()、 pthread_getspecific()、 pthread_setspecific()、 pthread_key_delete()等方法。
