1.引
这是atomic的源码,源码地址点 这里
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
... ...
... ...
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
源码里说明了 在getter和setter里添加了一把叫SpinLock的锁,保证了setter和getter的线程安全
2.那么啥是SpinLock呢?
了解一下SpinLock
中文叫自旋锁,当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放。所以,此锁比较适用于锁的持有者保存时间较短的情况下。
在OC里它是OSSpinLock,OS为前缀说明它是MacOS的API
OSSpinLock
这里有一个问题,为什么苹果使用自旋锁,而不使用其它的锁?
赋值操作是一个非常快速的操作,如果用互斥锁的话,让等待外面的线程休眠,再醒来,是一件多么痛苦的事情啊,好不容易睡着了,睡没几毫秒就得起来,会有起床气的!这里的起床气意思是消耗CPU资源~
3.atomic安全吗?
这是一道经典的面试题!-_-
看如下代码
@property (atomic, assign) NSInteger plus;//有一个atomic的Int数,用来自增
有一个自增的方法,循环10次,为了模拟线程抢夺的场景,我让当前线程睡1秒后执行自增然后打印当前线程以及自增后的值
- (void)plusMethod
{
for (NSInteger i = 0; i < 10; i++) {
sleep(1);
self.plus++;
NSLog(@"%@ plus:%ld", NSThread.currentThread, self.plus);
}
}
最后创建两条线程去操作这个自增方法
- (void)gcd
{
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[self plusMethod];
});
dispatch_async(queue, ^{
[self plusMethod];
});
}
想一下打印的结果,是不是0,1,2...19
打印的结果
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:2
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:2
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:4
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:3
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:5
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:6
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:8
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:8
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:9
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:10
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:11
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:12
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:13
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:14
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:15
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:15
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:16
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:17
<NSThread: 0x604000276080>{number = 3, name = (null)} plus:18
<NSThread: 0x60000047cc40>{number = 4, name = (null)} plus:18
结果的确是打印了20次,但是打印的结果并不是我们想象中的1~20,也就是说 这段 代码由线程安全的问题。
atomic并不是线程安全的,它只保证修饰的属性的set和get在多线程调用下是安全的,但是并不能保证数据的线程安全,
解决方法:添加一把锁
- (void)plusMethod
{
for (NSInteger i = 0; i < 10; i++) {
sleep(1);
OSSpinLockLock(&_spinLock);// <----
self.plus++;
NSLog(@"%@ plus:%ld", NSThread.currentThread, self.plus);
OSSpinLockUnlock(&_spinLock);// <-----
}
}
4.题外话
在Swift里,SpinLock是一个宏,它的值一把递归锁~