iOS开发-使用多线程同步锁@synchronized()的注意事项

3,108 阅读2分钟

1.@synchronized原理

synchronized中传入的object的内存地址,被用作key,通过hash map对应的一个系统维护的递归锁。所以不管是传入什么类型的object,只要是有内存地址,就能启动同步代码块的效果。

2.注意事项

  • synchronized是使用的递归mutex来做同步。
 NSObject *obj = [[NSObject alloc]init];
    @synchronized (obj) {
        NSLog(@"1st sync");
        @synchronized (obj) {
            NSLog(@"2nd sync");
        }
    }

执行结果:

2019-07-15 17:59:27.398820+0800 xxtest[63251:2852986] 1st sync
2019-07-15 17:59:27.399013+0800 xxtest[63251:2852986] 2nd sync
  • @synchronized(nil)不起任何作用 @synchronized(nil)不起任何作用,表明我们需要适当关注传入的object的声明周期,一旦置为nil之后就无法做代码同步了。
  • 慎用@synchronized(self) (尽量不要这样使用) 不要使用@synchronized(self)。 不少代码都是直接将self传入@synchronized当,容易导致死锁的出现。
//class A
@synchronized (self) {
    [_sharedLock lock];
    NSLog(@"code in class A");
    [_sharedLock unlock];
}

//class B
[_sharedLock lock];
@synchronized (objectA) {
    NSLog(@"code in class B");
}
[_sharedLock unlock];

原因是self很可能会被外部对象访问,被用作key来生成一锁,类似上述代码中的@synchronized (objectA)。两个公共锁交替使用的场景就容易出现死锁。

正确的做法是传入一个类内部维护的NSObject对象,而且这个对象是对外不可见的。

  • 注意粒度控制
@synchronized (sharedToken) {
    [arrA addObject:obj];
}

@synchronized (sharedToken) {
    [arrB addObject:obj];
}

使用同一个token来同步arrA和arrB的访问,虽然arrA和arrB之间没有任何联系。 应该是不同的数据使用不同的锁,尽量将粒度控制在最细的程度 应该优化成如下代码:

@synchronized (tokenA) {
    [arrA addObject:obj];
}

@synchronized (tokenB) {
    [arrB addObject:obj];
}
  • 注意内部的函数调用 @synchronized还有个很容易变慢的场景,就是{}内部有其他隐蔽的函数调用。比如:
@synchronized (tokenA) {
    [arrA addObject:obj];
    [self doSomethingWithA:arrA];
}

doSomethingWithA内部可能又调用了其他函数,维护doSomethingWithA的工程师可能并没有意识到自己是被锁同步的,由此层层叠叠可能引入更多的函数调用,代码就莫名其妙的越来越慢了,感觉锁的性能差,其实是我们没用好。