1. 程式人生 > >ios建立單例中的@synchronized和dispatch_once

ios建立單例中的@synchronized和dispatch_once

@synchronized和dispatch_once 在單例的使用如下:

static LvSingleClass * singleClass = nil;
+ (LvSingleClass *)sharedSingleClass {
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
          singleClass = [[LvSingleClass alloc] init];
     });
     return singleClass;
}

+ (LvSingleClass *)sharedSingleClass {
    @synchronized(self){
         if (singleClass == nil) {
             singleClass = [[LvSingleClass alloc]  init];
         }
     }
     return singleClass;
}

在OC中,@synchronize 是用NSRecursiveLock實現的,並且隱式添加了exception handler,如果有異常丟擲,exception handler 會自動釋放互斥鎖。
而dispatch_once 它省去了鎖的操作,用的是大量的原子操作,該原子操作內部不是靠pthread等鎖來實現的,而是利用lock的彙編指令,靠底層cpu指令來執行的 ,所以擁有高的效能,
//程式碼測試

  CFAbsoluteTime  dispatchStartTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0;i < 1000;i ++) {
        [SingleClass sharedSingle];
    }
    CFAbsoluteTime dispatchEndTime = CFAbsoluteTimeGetCurrent();
    NSLog(@"dispatch time:%f s",dispatchEndTime - dispatchStartTime);
    
    
    CFAbsoluteTime synchronizeStartTime = CFAbsoluteTimeGetCurrent();
    for (int i = 0;i < 1000 ;i++) {
        [SingleOneClass sharedSingle];
    }
    CFAbsoluteTime synchronizedEndTime = CFAbsoluteTimeGetCurrent();
    NSLog(@"synchronized time : %f s",synchronizedEndTime - synchronizeStartTime);

//測試結果

2018-10-31 14:49:22.313217+0800 Demo[20364:6801421] dispatch time:0.000047 s
2018-10-31 14:49:22.313530+0800 Demo[20364:6801421] synchronized time : 0.000116 s

可以發現 在單執行緒中可以發現dispatch_once方法的效能要明顯優於synchronized方法,所以在實際的應用中我們可以多采用dispatch_once方式來實現單例。

  • 下面來簡單瞭解下 @synchronized

    @synchronized指令的物件是用於區分受保護塊的唯一識別符號。如果在兩個不同的執行緒中執行上述方法,則anObj在每個執行緒上為引數傳遞一個不同的物件,每個執行緒都會鎖定並繼續處理,而不會被另一個阻塞。但是,如果在兩種情況下都傳遞相同的物件,則其中一個執行緒將首先獲取鎖定,另一個執行緒將阻塞,直到第一個執行緒完成關鍵部分。
    作為預防措施,該@synchronized塊隱式地向受保護程式碼新增異常處理程式。如果丟擲異常,此處理程式會自動釋放互斥鎖。這意味著為了使用該@synchronized指令,還必須在程式碼中啟用Objective-C異常處理。如果您不希望由隱式異常處理程式引起額外開銷,則應考慮使用鎖類。

  • 下面來簡單瞭解下 dispatch_once

      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
         //some code
      });
    

    onceToken 引數它保證了後面的 block 只被執行一遍。我們通過一個簡單的單例實現來看看 onceToken 在 dispatch_once 執行過程中的變化:

    + (SingleClass *)sharedSingle {
         static SingleClass * singleClass = nil;
         static dispatch_once_t onceToken;
         NSLog(@"Before onceToken = %ld", onceToken);
    
         dispatch_once(&onceToken, ^{
                singleClass = [[SingleClass alloc] init];
                NSLog(@"running onceToken = %ld", onceToken);
         });
         NSLog(@"After  onceToken = %ld", onceToken);
    
        return singleClass;
    }
    

    執行結果

    2018-10-31 16:05:05.306434+0800 Demo[20530:7311130] Before onceToken = 0
    2018-10-31 16:05:05.306574+0800 Demo[20530:7311130] running onceToken = 1024
    2018-10-31 16:05:05.306736+0800 Demo[20530:7311130] After  onceToken = -1
    

    通過輸出我們可以發現,在 dispatch_once 執行前,onceToken 的值是 0,因為 dispatch_once_t 是由 typedef long dispatch_once_t 而來,所以在 onceToken 還沒被手動賦值的情況下,0 是編譯器給 onceToken 的初始化賦值。在 dispatch_once 執行過程中,onceToken 是一個很大的數字,這個值是 dispath_once 內部實現中一個區域性變數的地址,並不是一個固定的值。當 dispatch_once 執行完畢,onceToken 的值被賦為 -1。