// 先看问题
- (NSString *)xua {
if (!_xua) {
@synchronized (_xua) {
if (!_xua) {
_xua = [TAFManager XUA];
}
}
}
return _xua;
}
这段代码有什么问题吗?为了保持线程同步,需要给对象加锁。对@synchronized:
防止不同的线程同时执行同一段代码。
结果
上面的线程会存在_xua
为nil
的情况(第一次获取值的时候),当被锁定的对象为nil时,其实@synchronized
是无作用的,也就是不能保证线程安全。
原理
我对 @synchronized
的实现十分好奇并搜了一些它的细节。我找到了一些答案,但这些解释都没有达到我想要的深度。锁是如何与你传入 @synchronized
的对象关联上的?@synchronized
会保持(retain,增加引用计数)被锁住的对象么?假如你传入 @synchronized
的对象在 @synchronized
的block
里面被释放或者被赋值为 nil 将会怎么样?这些全都是我想回答的问题。而我这次的收获,会要你好看。
@synchronized
的文档告诉我们, @synchronized block
在被保护的代码上暗中添加了一个异常处理。为的是同步某对象时如若抛出异常,锁会被释放掉。@synchronized block
会变成 objc_sync_enter
和 objc_sync_exit
的成对儿调用。我们不知道这些函数是干啥的,但基于这些事实我们可以认为编译器将这样的代码:
@synchronized(obj) {
// do work
}
转化成这样的东东
@try {
objc_sync_enter(obj);
// do work
} @finally {
objc_sync_exit(obj);
}
objc_sync_enter 和 objc_sync_exit 是啥?它们是如何实现的?在 Xcode 中按住 Command 键单击它们,然后进到了,里面有我们感兴趣的这两个函数:
/**
* 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);
不过,objc_sync_enter
的文档告诉我们一些新东西: @synchronized
结构在工作时为传入的对象分配了一个递归锁。分配工作何时发生,如何发生呢?它怎样处理 nil
?幸运的是 Objective-C runtime
是开源的,所以我们可以马上阅读源码并找到答案!
注:递归锁在被同一线程重复获取时不会产生死锁。
想深入了解,有个叫做 NSRecursiveLock 的现成的类也是这样的,你可以试试。
你可以在这里找到 objc-sync 的全部源码。