開文首先我要糾正一個網上常見的關於atomic非線程安全的舉例:若是線程 A 調了 getter,與此同時線程 B 、線程 C 都調了 setter——那最後線程 A get 到的值,有3種可能:多是 B、C set 以前原始的值,也多是 B set 的值,也多是 C set 的值。同時,最終這個屬性的值,多是 B set 的值,也有多是 C set 的值。因此atomic可並不能保證對象的線程安全。面試
相似的這個例子相信不少人都見過,看起來也很是合理,沒什麼錯;但細琢磨,這個例子自己沒問題,但根本不能證實atomic的非線程安全這個觀點!因此面試的時候若是舉這個例子~~說明你就沒明白atomic的非線程安全性!安全
下面是兩個nonatomic和atomic修飾的變量,咱們用代碼掩飾其內部實現;bash
@property (nonatomic) UIImage *nonImage;
@property (atomic) UIImage *atomicImage;
//nonatomic的setter和getter實現:
- (void)setNonImage:(UIImage *)nonImage
{
_nonImage = nonImage;
}
- (UIImage *)nonImage
{
return _nonImage;
}
//atomic的setter和getter實現:
- (void)setAtomicImage:(UIImage *)atomicImage
{
@synchronized (self) {
_atomicImage = atomicImage;
}
}
- (UIImage *)atomicImage
{
@synchronized (self) {
return _atomicImage;
}
}
複製代碼
其實如今一想很奇怪,爲何要把atomic和線程安全聯繫在一塊兒去探究;atomic只是對屬性的getter/setter方法進行了加鎖操做,這種安全僅僅是get/set的讀寫安全,僅此之一,可是線程安全還有除了讀寫的其餘操做,好比:當一個線程正在get/set時,另外一個線程同時進行release操做,可能會直接crash。很明顯atomic的讀寫鎖不能保證線程安全。 下面兩個例子寫的就挺好,挺簡單:\多線程
eg1:若是定義屬性NSInteger i是原子的,對i進行i = i + 1操做就是不安全的; 由於原子性只能保證讀寫安全,而該表達式須要三步操做:
一、讀取i的值存入寄存器;
二、將i加1;
三、修改i的值;
若是在第一步完成的時候,i被其餘線程修改了,那麼表達式執行的結果就與預期的不同,也就是不安全的 eg2:async
self.slice = 0;
dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i=0; i<10000; i++) {
self.slice = self.slice + 1;
}
});
dispatch_async(queue, ^{
for (int i=0; i<10000; i++) {
self.slice = self.slice + 1;
}
});
複製代碼
結果多是[10000,20000]之間的某個值,而咱們想要的結果是20000;很明顯這個例子就會引發線程隱患,而atomic並不能防止這個問題;因此咱們說atomic不是線程安全;
因此要想真正理解atomic的非線程安全性,必需要去官網查找解釋並經過源碼分析才行; 在runtime時property的atomic是一個booleau值,是採用spinlock_t鎖去實現的;源碼分析
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 (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
複製代碼
很明顯atomic屬性的setter/getter方法都被加了spinlock自旋鎖,須要注意的是spinlock已經因爲存在優先級反轉問題被棄用並用os_unfair_lock替代。既然被棄用了,這裏爲何還在用;緣由是進入spinlock去看會發現,底層已經被os_unfair_lick替換:ui
using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
os_unfair_lock mLock;
public:
constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) {
lockdebug_remember_mutex(this);
}
constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { }
void lock() {
lockdebug_mutex_lock(this);
os_unfair_lock_lock_with_options_inline
.
.
.
複製代碼