iOS-OC底層-@synchronized分析

前言

,在我們的iOS開發(fā)中還是經(jīng)常用到的翰苫,特別是在一些多線程的安全訪問方面提供了提供了便捷的方案止邮。,分為自旋鎖,互斥鎖,讀寫鎖等類型奏窑。在iOS下导披,我們常見的包括:@synchronized,NSLock,dispatch_semphore,NSCondition,NSConditionLock,NSRecursiveLock等。本篇文章我們就重點分析下@synchronized的本質(zhì)埃唯。

開始

首先從一個大家熟悉的賣票案例說起:

- (void)demo11
{
    _totalCount = 20;
    [self startSaleTicket];
}
- (void)startSaleTicket{
    dispatch_async(dispatch_queue_create("queue_1", DISPATCH_QUEUE_CONCURRENT), ^{
        for (int i = 0; i < 30; i++) {
            [self saleTicket];
        }
    });
    dispatch_async(dispatch_queue_create("queue_2", DISPATCH_QUEUE_CONCURRENT), ^{
        for (int i = 0; i < 15; i++) {
            [self saleTicket];
        }
    });
}

- (void)saleTicket{
    if (_totalCount > 0) {
        _totalCount--;
        sleep(0.1);
        NSLog(@"當(dāng)前余票還剩:%ld張",_totalCount);
    }else{
        NSLog(@"當(dāng)前車票已售罄");
    }
}

以上代碼表示有兩個并發(fā)隊列同時執(zhí)行異步的買票任務(wù)撩匕,這就意味著saleTicket在某個時刻或者多個時刻同時被多個任務(wù)訪問,而導(dǎo)致數(shù)據(jù)的不同步墨叛,從而導(dǎo)致數(shù)據(jù)的不安全止毕。輸出結(jié)果:

2020-11-09 16:29:05.728246+0800 TestApp[13533:7920231] 當(dāng)前余票還剩:18張
2020-11-09 16:29:05.728246+0800 TestApp[13533:7920234] 當(dāng)前余票還剩:19張
2020-11-09 16:29:05.728464+0800 TestApp[13533:7920234] 當(dāng)前余票還剩:17張
2020-11-09 16:29:05.728464+0800 TestApp[13533:7920231] 當(dāng)前余票還剩:17張
2020-11-09 16:29:05.728604+0800 TestApp[13533:7920234] 當(dāng)前余票還剩:16張
2020-11-09 16:29:05.728624+0800 TestApp[13533:7920231] 當(dāng)前余票還剩:15張
2020-11-09 16:29:05.728750+0800 TestApp[13533:7920234] 當(dāng)前余票還剩:13張
2020-11-09 16:29:05.728752+0800 TestApp[13533:7920231] 當(dāng)前余票還剩:13張
2020-11-09 16:29:05.728925+0800 TestApp[13533:7920234] 當(dāng)前余票還剩:12張
2020-11-09 16:29:05.729969+0800 TestApp[13533:7920231] 當(dāng)前余票還剩:11張
2020-11-09 16:29:05.730211+0800 TestApp[13533:7920234] 當(dāng)前余票還剩:10張
...............省略一些打印數(shù)據(jù)...............
2020-11-09 16:29:05.733704+0800 TestApp[13533:7920231] 當(dāng)前余票還剩:1張
2020-11-09 16:29:05.734041+0800 TestApp[13533:7920234] 當(dāng)前余票還剩:0張
2020-11-09 16:29:05.735104+0800 TestApp[13533:7920231] 當(dāng)前車票已售罄
2020-11-09 16:29:05.735527+0800 TestApp[13533:7920234] 當(dāng)前車票已售罄
2020-11-09 16:29:05.735923+0800 TestApp[13533:7920231] 當(dāng)前車票已售罄

從輸出的結(jié)果可以看到,整個數(shù)據(jù)一開始就發(fā)生了錯誤巍实。接下來我們使用@synchronizedsaleTicket加鎖然后再執(zhí)行:

- (void)saleTicket{
    @synchronized (self) {
        if (_totalCount > 0) {
            _totalCount--;
            sleep(0.1);
            NSLog(@"當(dāng)前余票還剩:%ld張",_totalCount);
        }else{
            NSLog(@"當(dāng)前車票已售罄");
        }
    }
}
2020-11-09 16:31:46.802605+0800 TestApp[13600:7921875] 當(dāng)前余票還剩:19張
2020-11-09 16:31:46.802823+0800 TestApp[13600:7921875] 當(dāng)前余票還剩:18張
2020-11-09 16:31:46.803009+0800 TestApp[13600:7921875] 當(dāng)前余票還剩:17張
2020-11-09 16:31:46.803180+0800 TestApp[13600:7921874] 當(dāng)前余票還剩:16張
2020-11-09 16:31:46.803327+0800 TestApp[13600:7921874] 當(dāng)前余票還剩:15張
2020-11-09 16:31:46.803493+0800 TestApp[13600:7921875] 當(dāng)前余票還剩:14張
................省略一些打印數(shù)據(jù).................
2020-11-09 16:31:46.809983+0800 TestApp[13600:7921874] 當(dāng)前余票還剩:1張
2020-11-09 16:31:46.810512+0800 TestApp[13600:7921874] 當(dāng)前余票還剩:0張
2020-11-09 16:31:46.811509+0800 TestApp[13600:7921874] 當(dāng)前車票已售罄
2020-11-09 16:31:46.812906+0800 TestApp[13600:7921874] 當(dāng)前車票已售罄
2020-11-09 16:31:46.813443+0800 TestApp[13600:7921874] 當(dāng)前車票已售罄

此時的打印結(jié)果就不會出現(xiàn)異常的數(shù)據(jù)了滓技。那@synchronized是怎么做到的吶?內(nèi)部都做了些什么棚潦?
我們首先通過斷點并打開Always Show Disassembly來看下匯編的信息令漂,是否有一些線索:

截屏2020-11-09 下午4.41.21.png

截屏2020-11-09 下午4.42.45.png

從匯編中,能看到的是在執(zhí)行前后多了objc_sync_enterobjc_sync_exit指令,可能有用先記錄下叠必。
接下來我們通過clang來看下@synchronized最終轉(zhuǎn)化為了什么:
首先荚孵,我們在main.m中,添加幾行代碼:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    @synchronized (appDelegateClassName) {
        NSLog(@"@synchronized");
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

通過clang編譯后的代碼:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
    }
    { id _rethrow = 0; id _sync_obj = (id)appDelegateClassName; objc_sync_enter(_sync_obj);
try {
    struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
    ~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
    id sync_exit;
    } _sync_exit(_sync_obj);

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_yy_htpy_x9s09v1zf7ms0jgytwr0000gn_T_main_54c387_mi_0);
    } catch (id e) {_rethrow = e;}
{ struct _FIN { _FIN(id reth) : rethrow(reth) {}
    ~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
    id rethrow;
    } _fin_force_rethow(_rethrow);}
}

    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

比較凌亂纬朝,我們整理一下:

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */
    { __AtAutoreleasePool __autoreleasepool;
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
    }
    /* @synchronized */
    {
        id _rethrow = 0;
        id _sync_obj = (id)appDelegateClassName;
        objc_sync_enter(_sync_obj);
        try {
            struct _SYNC_EXIT {
                _SYNC_EXIT(id arg) : sync_exit(arg) {}
                ~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
                id sync_exit;
            }
            _sync_exit(_sync_obj);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yy_htpy_x9s09v1zf7ms0jgytwr0000gn_T_main_54c387_mi_0);
            
        } catch (id e) {
            _rethrow = e;
        }
        {
            struct _FIN {
                _FIN(id reth) : rethrow(reth) {}
                ~_FIN() { if (rethrow) objc_exception_throw(rethrow);}
                id rethrow;
            }
            _fin_force_rethow(_rethrow);
        }
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

這里第一個作用域的內(nèi)容是關(guān)于@autoreleasepool相關(guān)的收叶,關(guān)于這部分內(nèi)容可以直接看我的iOS-OC底層-@autoreleasepool分析 這篇文章。第二個作用域中的內(nèi)容就是關(guān)系@synchronized了共苛,還是挺復(fù)雜的??判没。這里我們并不需要看太多:
1.首先需要注意到的是objc_sync_enter(_sync_obj);這跟在匯編看到的是一致的
2.然后是一個try{}包裹邏輯,內(nèi)部有一個struct _SYNC_EXIT結(jié)構(gòu)體隅茎,并且包換一個構(gòu)造方法_SYNC_EXIT(id arg)和析構(gòu)方法~_SYNC_EXIT(),
3._sync_exit(_sync_obj);初始化結(jié)構(gòu)體執(zhí)行_SYNC_EXIT(id arg)
4.NSLog()澄峰,這是需要加鎖的代碼塊主體。
5.當(dāng)try{}執(zhí)行完時辟犀,會觸發(fā)結(jié)構(gòu)體的~_SYNC_EXIT()的析構(gòu)方法俏竞,即objc_sync_exit(sync_exit)方法。
所以這里簡單的理解就是:
objc_sync_enter(_sync_obj) ->代碼主體 -> objc_sync_exit(sync_exit)
catch部分我們就不做分析了,當(dāng)發(fā)生異常后會執(zhí)行objc_exception_throw堂竟。
到此魂毁,我們需要探索的重點就鎖定在了objc_sync_enter()objc_sync_exit()的兩個方法的具體實現(xiàn)中來,通過匯編跟蹤下這兩個方法在哪個庫中(以objc開頭出嘹,通常是在libobjc.dylib中):

截屏2020-11-09 下午5.17.58.png

果不其然席楚,確實就在libobjc中,查看源碼:

// Begin synchronizing on 'obj'. 
// Allocates recursive mutex associated with 'obj' if needed.
// Returns OBJC_SYNC_SUCCESS once lock is acquired.  
int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        ASSERT(data);
        data->mutex.lock();
    } else {
        // @synchronized(nil) does nothing
        if (DebugNilSync) {
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        }
        objc_sync_nil();
    }
    return result;
}

這里的參數(shù)id obj其實就是@synchronized時傳入的參數(shù)疚漆;判斷obj是否存在酣胀,如果存在,就通過id2data()方法讀取一個SyncData* data娶聘,然后通過data->mutex.lock()來上鎖榨惠。這里要說一下mutex:
mutex:用于保證在任何時刻吹散,都只能有一個線程訪問該對象误堡。 當(dāng)獲取鎖操作失敗時佑颇,線程會進入睡眠,等待鎖釋放時被喚醒狡耻。
這也就意味著墩剖,@synchronized實質(zhì)上是一種(遞歸)互斥鎖。
而當(dāng)obj == nil時夷狰,即@synchronized(nil),看else內(nèi)部注釋:@synchronized(nil) does nothing
這里還是要看下objc_sync_nil()做了什么:

BREAKPOINT_FUNCTION(
    void objc_sync_nil(void)
);
#   define BREAKPOINT_FUNCTION(prototype)                             \
    OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
    prototype { asm(""); }

這里是一個宏定義的實現(xiàn)岭皂,但內(nèi)部也沒有做什么實質(zhì)性的處理。

// End synchronizing on 'obj'. 
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    if (obj) {
        SyncData* data = id2data(obj, RELEASE); 
        if (!data) {
            result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
        } else {
            bool okay = data->mutex.tryUnlock();
            if (!okay) {
                result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
            }
        }
    } else {
        // @synchronized(nil) does nothing
    }
    return result;
}

再看下int objc_sync_exit(id obj)沼头,當(dāng)obj存在爷绘,同樣會通過id2data(obj, RELEASE)去獲取SyncData* data,如果存在,會通過data->mutex.tryUnlock();去解鎖书劝。當(dāng)obj不存在,則dose nothing土至。
從上面的源碼中我們可以發(fā)現(xiàn)购对,兩個方法內(nèi)部都調(diào)用了同樣的方法id2data(),
只是第二個傳參有所不同,一個是ACQUIRE陶因,一個是RELEASE骡苞。我們重點看下id2data()方法:
先預(yù)覽下這部分代碼:

截屏2020-11-09 下午5.49.18.png

我們把代碼大致分成如圖的4個部分:

  1. #if SUPPORT_DIRECT_THREAD_KEYS .. #endif部分
  2. SyncCache *cache = fetch_cache(NO);部分
  3. lockp->lock();lockp->unlock(); 部分
  4. if(result)部分

這里我們要注意代碼注釋還是非常有價值的,首先是判斷是否支持DIRECT_THREAD_KEYS的方式讀取緩存楷扬,如果不支持就通過per-thread cache讀取緩存解幽;如果緩存中不存在則嘗試通過in-use list查找,找到后插入緩存毅否;如果都沒有亚铁,即第一次進入,則通過posix_memalign()創(chuàng)建新的SyncData螟加,最終寫入緩存。

static SyncData* id2data(id object, enum usage why)
{
    spinlock_t *lockp = &LOCK_FOR_OBJ(object);
    SyncData **listp = &LIST_FOR_OBJ(object);
    SyncData* result = NULL;

#if SUPPORT_DIRECT_THREAD_KEYS
    // Check per-thread single-entry fast cache for matching object
    bool fastCacheOccupied = NO;
    SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
    if (data) {
        fastCacheOccupied = YES;

        if (data->object == object) {
            // Found a match in fast cache.
            uintptr_t lockCount;

            result = data;
            lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
            if (result->threadCount <= 0  ||  lockCount <= 0) {
                _objc_fatal("id2data fastcache is buggy");
            }

            switch(why) {
            case ACQUIRE: {
                lockCount++;
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                break;
            }
            case RELEASE:
                lockCount--;
                tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
                if (lockCount == 0) {
                    // remove from fast cache
                    tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
                    // atomic because may collide with concurrent ACQUIRE
                    OSAtomicDecrement32Barrier(&result->threadCount);
                }
                break;
            case CHECK:
                // do nothing
                break;
            }

            return result;
        }
    }
#endif

    // Check per-thread cache of already-owned locks for matching object
    SyncCache *cache = fetch_cache(NO);
    if (cache) {
        unsigned int i;
        for (i = 0; i < cache->used; i++) {
            SyncCacheItem *item = &cache->list[i];
            if (item->data->object != object) continue;

            // Found a match.
            result = item->data;
            if (result->threadCount <= 0  ||  item->lockCount <= 0) {
                _objc_fatal("id2data cache is buggy");
            }
                
            switch(why) {
            case ACQUIRE:
                item->lockCount++;
                break;
            case RELEASE:
                item->lockCount--;
                if (item->lockCount == 0) {
                    // remove from per-thread cache
                    cache->list[i] = cache->list[--cache->used];
                    // atomic because may collide with concurrent ACQUIRE
                    OSAtomicDecrement32Barrier(&result->threadCount);
                }
                break;
            case CHECK:
                // do nothing
                break;
            }

            return result;
        }
    }

    // Thread cache didn't find anything.
    // Walk in-use list looking for matching object
    // Spinlock prevents multiple threads from creating multiple 
    // locks for the same new object.
    // We could keep the nodes in some hash table if we find that there are
    // more than 20 or so distinct locks active, but we don't do that now.
    
    lockp->lock();

    {
        SyncData* p;
        SyncData* firstUnused = NULL;
        for (p = *listp; p != NULL; p = p->nextData) {
            if ( p->object == object ) {
                result = p;
                // atomic because may collide with concurrent RELEASE
                OSAtomicIncrement32Barrier(&result->threadCount);
                goto done;
            }
            if ( (firstUnused == NULL) && (p->threadCount == 0) )
                firstUnused = p;
        }
    
        // no SyncData currently associated with object
        if ( (why == RELEASE) || (why == CHECK) )
            goto done;
    
        // an unused one was found, use it
        if ( firstUnused != NULL ) {
            result = firstUnused;
            result->object = (objc_object *)object;
            result->threadCount = 1;
            goto done;
        }
    }

    // Allocate a new SyncData and add to list.
    // XXX allocating memory with a global lock held is bad practice,
    // might be worth releasing the lock, allocating, and searching again.
    // But since we never free these guys we won't be stuck in allocation very often.
    posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
    result->object = (objc_object *)object;
    result->threadCount = 1;
    new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
    result->nextData = *listp;
    *listp = result;
    
 done:
    lockp->unlock();
    if (result) {
        // Only new ACQUIRE should get here.
        // All RELEASE and CHECK and recursive ACQUIRE are 
        // handled by the per-thread caches above.
        if (why == RELEASE) {
            // Probably some thread is incorrectly exiting 
            // while the object is held by another thread.
            return nil;
        }
        if (why != ACQUIRE) _objc_fatal("id2data is buggy");
        if (result->object != object) _objc_fatal("id2data is buggy");

#if SUPPORT_DIRECT_THREAD_KEYS
        if (!fastCacheOccupied) {
            // Save in fast thread cache
            tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
            tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
        } else 
#endif
        {
            // Save in thread cache
            if (!cache) cache = fetch_cache(YES);
            cache->list[cache->used].data = result;
            cache->list[cache->used].lockCount = 1;
            cache->used++;
        }
    }

    return result;
}

首先看第1部分:
這里先擴展一下TLS相關(guān)的概念:線程局部存儲(Thread Local Storage,TLS)吞琐,是操作系統(tǒng)為線程單獨提供的私有空間捆探,通常只有有限的容量。通常通過pthread庫中的:
pthread_key_create(),
pthread_getspecific(),
pthread_setspecific(),
pthread_key_delete()
等API來存取一些數(shù)據(jù)或保存一些狀態(tài)站粟。簡單的理解就是給線程也提供了一些KVC的存取操作的能力黍图。
這里主要存儲就是兩個值SYNC_DATA_DIRECT_KEY:SyncData *data
SYNC_COUNT_DIRECT_KEY:uintptr_t lockCount
這里先看下SyncData的數(shù)據(jù)結(jié)構(gòu):

typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;

SyncData內(nèi)部實際是是一個單鏈表結(jié)構(gòu)奴烙,每一個結(jié)構(gòu)都會指向nextData;
threadCount來記錄當(dāng)前線程數(shù)助被,object即傳入的參數(shù),mutex核心的鎖結(jié)構(gòu)切诀。
接下來揩环,如果找到了data,將data賦值給result,同時將lockCount ++或者lockCount--幅虑,并通過tls_set_direct()更新到對應(yīng)的key丰滑。當(dāng)lockCount == 0時,會將線程緩存值SYNC_DATA_DIRECT_KEY置為NULL倒庵,同時threadCount --;
第2部分:
這里其實還是讀取緩存,還是先看下SyncCache數(shù)據(jù)結(jié)構(gòu):

typedef struct {
    SyncData *data;
    unsigned int lockCount;  // number of times THIS THREAD locked this block
} SyncCacheItem;

typedef struct SyncCache {
    unsigned int allocated;
    unsigned int used;
    SyncCacheItem list[0];
} SyncCache;

只是這里查詢的結(jié)構(gòu)有所不同褒墨,其最終還是去查詢SyncDatalockCount。然后其他的增減邏輯就一模一樣了擎宝。
第三部分:

//Thread cache didn't find anything.
//Walk in-use list looking for matching object

第四部分:

// Allocate a new SyncData and add to list.
首次執(zhí)行郁妈,新創(chuàng)建 SyncData,
// Save in fast thread cache
// Save in thread cache

最后通過一個圖標(biāo)來總結(jié)下:

圖1

圖2 內(nèi)部關(guān)聯(lián)邏輯

從圖中可以看出:

  • 鎖的內(nèi)部首先是一個SyncCache結(jié)構(gòu)體,包含著一個list數(shù)組绍申,存放著SyncCacheItem類型的數(shù)據(jù)噩咪,該元素表示某一個線程被鎖定的任務(wù)锄奢;
  • SyncCacheItem結(jié)構(gòu)包含一個SyncData *data,和int lockCount,其中lockCount記錄這這條線程上鎖的總數(shù),data記錄著被鎖定的任務(wù)剧腻;
  • SyncData,內(nèi)部包含了一個nextData拘央,也是SyncData類型,表示下一個被鎖定的任務(wù)书在,所以這里是一個單鏈表的結(jié)構(gòu)灰伟,來記錄整個任務(wù)鏈。
使用中的注意事項

@synchronized儒旬,由于需要各種鏈表的查詢栏账,創(chuàng)建等操作,所以在整個iOS鎖的性能排名上還是比較靠后的栈源。但由于它使用起來非常的簡單挡爵,而且不需要做額外的上鎖解鎖操作,所以在項目中使用頻率還是挺高的甚垦。
先寫一個案例:

- (void)demo33
{
    for (int i = 0; i < 100000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            self->_testArray = [NSMutableArray array];
        });
    }
}

在執(zhí)行demo33時茶鹃,會發(fā)生EXC_ACCESS崩潰

截屏2020-11-10 下午4.32.49.png

這是由于多個異步線程在給self.testArray賦值,即retain新值艰亮,release舊值闭翩,但在某一個瞬間,retainrelease在多個異步線程的情況下迄埃,發(fā)生資源搶占疗韵,導(dǎo)致沒有成對執(zhí)行,而導(dǎo)致了release野指針的情況侄非,如下:

2020-11-10 16:37:08.198551+0800 TestApp[45349:8517346] @synchronized
2020-11-10 16:37:08.364108+0800 TestApp[45349:8517491] *** -[__NSArrayM release]: message sent to deallocated instance 0x6000032b0ae0
2020-11-10 16:37:08.364115+0800 TestApp[45349:8517494] *** -[__NSArrayM release]: message sent to deallocated instance 0x60000324cea0

接下來蕉汪,我們給代碼添加一個@synchronized

- (void)demo33
{
    for (int i = 0; i < 100000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            @synchronized (self.testArray) {
                self.testArray = [NSMutableArray array];
            }
        });
    }
}

此時還是發(fā)生了崩潰,

截屏2020-11-10 下午4.40.16.png

同樣是由于objc_release去釋放了一個野指針導(dǎo)致逞怨。
這里要注意的就是@synchronized()的參數(shù)了者疤,self.testArray在多次的賦值過程中,在某個瞬間可能為nil骇钦,而@synchronized(nil)意味著加鎖失敗宛渐,就相當(dāng)于沒有鎖,所以這里崩潰的情況就跟之前的一樣了眯搭。
再次修改代碼窥翩,把@synchronized()傳入的參數(shù)修改為self:

- (void)demo33
{
    for (int i = 0; i < 100000; i++) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            @synchronized (self) {
                self.testArray = [NSMutableArray array];
            }
        });
    }
}

此時執(zhí)行代碼,結(jié)果顯示正常鳞仙。
所以這里要注意寇蚊,當(dāng)使用@synchronized鎖時,要保證該鎖傳入的參數(shù)在使用期間不能為nil棍好,否則鎖將失去意義仗岸,比如這里使用生命周期更長的self允耿,但這里不僅限于self,只要保證在使用期間不為nil就是可以的。再者就是基于@synchronized的性能表現(xiàn)不是很好扒怖,所以還是盡量選擇其他鎖來替代它较锡。

總結(jié)

這篇文章有問題,寫的不好5裂鳌B煸獭!俯邓!摸不著頭腦B饴ァ!;蕖鸟整!謹慎閱讀!k獭@禾酢!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梦重,一起剝皮案震驚了整個濱河市兑燥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌琴拧,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘱支,死亡現(xiàn)場離奇詭異蚓胸,居然都是意外死亡,警方通過查閱死者的電腦和手機除师,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門沛膳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人汛聚,你說我怎么就攤上這事锹安。” “怎么了倚舀?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵叹哭,是天一觀的道長。 經(jīng)常有香客問我痕貌,道長风罩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任舵稠,我火速辦了婚禮超升,結(jié)果婚禮上入宦,老公的妹妹穿的比我還像新娘。我一直安慰自己室琢,他們只是感情好乾闰,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盈滴,像睡著了一般涯肩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雹熬,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天宽菜,我揣著相機與錄音,去河邊找鬼竿报。 笑死铅乡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烈菌。 我是一名探鬼主播阵幸,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芽世!你這毒婦竟也來了挚赊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤济瓢,失蹤者是張志新(化名)和其女友劉穎荠割,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旺矾,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蔑鹦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了箕宙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嚎朽。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖柬帕,靈堂內(nèi)的尸體忽然破棺而出哟忍,到底是詐尸還是另有隱情,我是刑警寧澤陷寝,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布锅很,位于F島的核電站,受9級特大地震影響盼铁,放射性物質(zhì)發(fā)生泄漏粗蔚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一饶火、第九天 我趴在偏房一處隱蔽的房頂上張望鹏控。 院中可真熱鬧致扯,春花似錦、人聲如沸当辐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缘揪。三九已至耍群,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間找筝,已是汗流浹背蹈垢。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留袖裕,地道東北人曹抬。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像急鳄,于是被迫代替她去往敵國和親谤民。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1. 為什么多線程需要鎖疾宏? 首先在多線程處理的時候我們經(jīng)常會需要保證同步张足,這是為啥呢,看一下下面這個例子: 這種時...
    木小易Ying閱讀 1,046評論 0 8
  • iOS 中的鎖(1) 本文主要通過Objective-C語言進行體現(xiàn)坎藐,其實跟Swift也差不多为牍。 本文從鎖的基本概...
    just東東閱讀 550評論 0 0
  • 歡迎閱讀iOS探索系列(按序閱讀食用效果更加)iOS探索 alloc流程iOS探索 內(nèi)存對齊&malloc源碼iO...
    呂子喬_eabd閱讀 1,130評論 0 2
  • 寫在前面 多線程在日常開發(fā)中能起到性能優(yōu)化的作用,但是一旦沒用好就會造成線程不安全岩馍,本文就來講講如何保證線程安全 ...
    M_慕宸閱讀 532評論 0 5
  • 一. NSConditionLock NSConditionLock是對NSCondition的進一步封裝吵聪,可以設(shè)...
    Imkata閱讀 779評論 0 0