// 先看問(wèn)題
- (NSString *)xua {
if (!_xua) {
@synchronized (_xua) {
if (!_xua) {
_xua = [TAFManager XUA];
}
}
}
return _xua;
}
這段代碼有什么問(wèn)題嗎?為了保持線程同步,需要給對(duì)象加鎖。對(duì)@synchronized: 防止不同的線程同時(shí)執(zhí)行同一段代碼。
結(jié)果
上面的線程會(huì)存在_xua為nil的情況(第一次獲取值的時(shí)候),當(dāng)被鎖定的對(duì)象為nil時(shí),其實(shí)@synchronized是無(wú)作用的,也就是不能保證線程安全。
原理
我對(duì) @synchronized的實(shí)現(xiàn)十分好奇并搜了一些它的細(xì)節(jié)。我找到了一些答案,但這些解釋都沒(méi)有達(dá)到我想要的深度。鎖是如何與你傳入 @synchronized 的對(duì)象關(guān)聯(lián)上的?@synchronized會(huì)保持(retain,增加引用計(jì)數(shù))被鎖住的對(duì)象么?假如你傳入 @synchronized 的對(duì)象在 @synchronized 的block里面被釋放或者被賦值為 nil 將會(huì)怎么樣?這些全都是我想回答的問(wèn)題。而我這次的收獲,會(huì)要你好看。
@synchronized 的文檔告訴我們, @synchronized block 在被保護(hù)的代碼上暗中添加了一個(gè)異常處理。為的是同步某對(duì)象時(shí)如若拋出異常,鎖會(huì)被釋放掉。@synchronized block 會(huì)變成 objc_sync_enter和 objc_sync_exit的成對(duì)兒調(diào)用。我們不知道這些函數(shù)是干啥的,但基于這些事實(shí)我們可以認(rèn)為編譯器將這樣的代碼:
@synchronized(obj) {
// do work
}
轉(zhuǎn)化成這樣的東東
@try {
objc_sync_enter(obj);
// do work
} @finally {
objc_sync_exit(obj);
}
objc_sync_enter 和 objc_sync_exit 是啥?它們是如何實(shí)現(xiàn)的?在 Xcode 中按住 Command 鍵單擊它們,然后進(jìn)到了,里面有我們感興趣的這兩個(gè)函數(shù):
/**
* Begin synchronizing on 'obj'.
* 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'.
*
* @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);
不過(guò),objc_sync_enter的文檔告訴我們一些新東西: @synchronized 結(jié)構(gòu)在工作時(shí)為傳入的對(duì)象分配了一個(gè)遞歸鎖。分配工作何時(shí)發(fā)生,如何發(fā)生呢?它怎樣處理 nil?幸運(yùn)的是 Objective-C runtime 是開(kāi)源的,所以我們可以馬上閱讀源碼并找到答案!
注:遞歸鎖在被同一線程重復(fù)獲取時(shí)不會(huì)產(chǎn)生死鎖。
想深入了解,有個(gè)叫做 NSRecursiveLock 的現(xiàn)成的類(lèi)也是這樣的,你可以試試。
你可以在這里找到 objc-sync 的全部源碼。