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的工程师可能并没有意识到自己是被锁同步的,由此层层叠叠可能引入更多的函数调用,代码就莫名其妙的越来越慢了,感觉锁的性能差,其实是我们没用好。