通常,@synchronized 保证线程安全,但仅在正确使用时。递归获取锁也是安全的,尽管我在回答 here 中有详细说明。
@synchronized有几种常用的错误用法。这些是最常见的:
使用@synchronized 确保创建原子对象。
- (NSObject *)foo {
@synchronized(_foo) {
if (!_foo) {
_foo = [[NSObject alloc] init];
}
return _foo;
}
}
因为第一次获得锁时_foo 将为零,所以不会发生锁定,并且多个线程可能会在第一次完成之前创建自己的_foo。
每次使用@synchronized 锁定一个新对象。
- (void)foo {
@synchronized([[NSObject alloc] init]) {
[self bar];
}
}
我已经看过很多此代码,以及等效的 C# lock(new object()) {..}。由于它每次都尝试锁定一个新对象,因此它总是被允许进入代码的关键部分。这不是某种代码魔术。它绝对不会确保线程安全。
最后,锁定self。
- (void)foo {
@synchronized(self) {
[self bar];
}
}
虽然本身不是问题,但如果您的代码使用任何外部代码或本身就是一个库,则可能是一个问题。虽然该对象在内部称为self,但它在外部有一个变量名称。如果外部代码调用@synchronized(_yourObject) {...} 而你调用@synchronized(self) {...},你可能会发现自己陷入了僵局。最好创建一个不暴露在对象外部的内部对象来锁定。在您的 init 函数中添加 _lockObject = [[NSObject alloc] init]; 便宜、简单且安全。
编辑:
我仍然被问到关于这篇文章的问题,所以这里有一个例子说明为什么在实践中使用 @synchronized(self) 是个坏主意。
@interface Foo : NSObject
- (void)doSomething;
@end
@implementation Foo
- (void)doSomething {
sleep(1);
@synchronized(self) {
NSLog(@"Critical Section.");
}
}
// Elsewhere in your code
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Foo *foo = [[Foo alloc] init];
NSObject *lock = [[NSObject alloc] init];
dispatch_async(queue, ^{
for (int i=0; i<100; i++) {
@synchronized(lock) {
[foo doSomething];
}
NSLog(@"Background pass %d complete.", i);
}
});
for (int i=0; i<100; i++) {
@synchronized(foo) {
@synchronized(lock) {
[foo doSomething];
}
}
NSLog(@"Foreground pass %d complete.", i);
}
应该很清楚为什么会发生这种情况。锁定 foo 和 lock 在前台 VS 后台线程上以不同的顺序调用。说这是不好的做法很容易,但如果Foo 是一个库,用户不太可能知道代码包含一个锁。