ios中的鎖(Lock) NSLock,NSConditionLock,NSRecursiveLock,NSCondition

多線程編程中經常會碰到多個線程訪問一個變量的問題,那麼我們先來熟悉下我們跟線程相關的修飾符nonatomicatomic一搜索,會有很多文章;但是這些文章有一個共同的特點那就是nonatomic多線程不安全atomic多線程安全如何來判斷線程安全或不安全?對於小公司在大多數項目說的簡單點安全就是不報錯,不安全就是報錯我寫了個demo驗證了下

@property (strong, nonatomic) NSMutableArray *arrList1;
@property (strong, atomic)    NSMutableArray *arrList2;

定義了兩個變量最大的不同是一個用nonatomic,一個使用atomic然後,我開啓了多個線程

- (void)viewDidLoad {
    [super viewDidLoad];
    self.arrList1 = [[NSMutableArray alloc] init];
    self.arrList2 = [[NSMutableArray alloc] init];
    thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL];
    thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject2) object:NULL];
    thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject3) object:NULL];
    thread4 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject3) object:NULL];

}
-(void)addObject1{
    for (int i = 0; i < 50; ++i) {
        [self.arrList1 addObject:@"1111"];
        NSLog(@"%@",self.arrList1);
    }
}
-(void)addObject2{
    for (int i = 0; i < 50; ++i) {
        [self.arrList1 addObject:@"aaaa"];
        NSLog(@"%@",self.arrList1);
    }
}

-(void)addObject3{
    for (int i = 0; i < 50; ++i) {
        [self.arrList1 addObject:@"AAAA"];
        NSLog(@"%@",self.arrList1);
    }
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    [thread1 start];
    [thread2 start];
    [thread3 start];
}

三個線程都很給面子的訪問兩個不同的NSMutableArray確沒有報錯說好了線程不安全爲啥支持多線程訪問了?不知道是OC做了修改還是其他原因,怎麼跟Java中StringBuffer和StringBuilder一樣???說好了不安全,怎麼可以多線程訪問 ???不解


上面扯到的東西貌似跟今天我們講到的鎖沒什麼聯繫,但是如果你繼續搜索下去的話不少人介紹說跟鎖有關係,但是我看了很多篇日誌或文章沒找到一條論證的代碼,全都是文字介紹—這就扯犢子了,沒有代碼說明所有的結論都是廢的


:我們可以理解爲 變量在某一段時間只能被一個線程訪問 貌似這個概念抽象的一塌糊塗;來段代碼加日誌截圖,說說情況和原因
在上面的基礎上,方便看到日誌;我們把代碼改爲循環10次


-(void)addObject1{
    for (int i = 0; i < 10; ++i) {
        [arrList addObject:@"1111"];
        NSLog(@"%@" ,arrList);
    }
}
-(void)addObject2{
    for (int i = 0; i < 10; ++i) {
        [arrList addObject:@"AAAA"];
        NSLog(@"%@" ,arrList);
    }
}
-(void)addObject3{
    for (int i = 0; i < 10; ++i) {
        [arrList addObject:@"aaaa"];
        NSLog(@"%@" ,arrList);
    }
}

注意這裏是把循環50次變爲循環10次,爲了看到日誌;三個方法分別對應三個線程;我們的理解是線程1,2,3誰先執行是不確定的但是按照我們的感覺是輸出的結果:
1111111111AAAAAAAAAAaaaaaaaaaa
或者
AAAAAAAAAA1111111111aaaaaaaaaa
或者類似這種排列,也就是輸出連貫的,再執行下一個連貫的循環輸出 ,但是我們看下我們的日誌
這裏寫圖片描述

看到這樣的日誌輸出,我們彷彿對多線程有了那麼一點點的瞭解,for循環體還沒有結束而循環體裏面的可變數組卻被兩個的線程操作,這個就尷尬了那麼如何得到上面順序排列的值了,比如:AAAAAAAAAA1111111111aaaaaaaaaa;那麼我們需要做的就是加鎖防止循環沒有結束而被另外的線程訪問,我們來介紹下ios中的所謂的鎖


1. 關鍵字synchronized

如果從Java轉過來,那麼很容易理解了 ,Java中這個關鍵字使用的比較頻繁;這個關鍵字是修飾一個對象的,這個很關鍵!!!

 -(void)addObject1{
    @synchronized (arrList) {
        for (int i = 0; i < 10; ++i) {
            [arrList addObject:@"1111"];
            NSLog(@"%@" ,arrList);
        }
    }
}

同樣在addObject2和addObject3方法中也要這樣寫,這樣寫神馬意思了 ? synchronized 是同步的意思,或者這樣理解通過synchronized arrList關起來等這段代碼(被synchronized大括號括起來的代碼 )執行完之後再放出來,很顯然關起來的時候只允許一個線程對它操作,來張截圖
這裏寫圖片描述
這結果簡直就是強迫症的福音

2. NSLock

這是OC中的一把鎖,那麼跟剛剛提到的synchronized有什麼區別了 ?剛剛上文提到synchronized只能給某一個對象加鎖,而NSLock可以個一段代碼加鎖;首先簡單的看下OC中有幾把鎖
這裏寫圖片描述

一共四把鎖:NSLock、 NSConditionLock、 NSRecursiveLock 、NSCondition;但是他們不是繼承某一個類,而是都實現了NSLocking這個代理,那麼必須看看這個代碼有神馬東西

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end

這個代理還是比較老實的,就提供兩個方法:加鎖(lock)和解鎖(unlock)

2.1 NSLock普通鎖

剛剛上面說過NSLock是鎖一段代碼的,那麼這個就比較簡單了,首先在ViewDidLoad中初始化NSLock ;然後使用這個鎖,上代碼

- (void)viewDidLoad {
    [super viewDidLoad];
    /*其他的代碼省略三千個字...*/
    nslock = [[NSLock alloc] init];
}
-(void)addObject1{
   [nslock lock];
        for (int i = 0; i < 10; ++i) {
            [arrList addObject:@"1111"];
            NSLog(@"%@", arrList);
        }
   [nslock unlock];
}
-(void)addObject2{
   [nslock lock];
        for (int i = 0; i < 10; ++i) {
            [arrList addObject:@"AAAA"];
            NSLog(@"%@", arrList);
        }
   [nslock unlock];
}
-(void)addObject3{
   [nslock lock];
        for (int i = 0; i < 10; ++i) {
            [arrList addObject:@"aaaa"];
            NSLog(@"%@", arrList);
        }
   [nslock unlock];
}

也就是 從 [nslock lock] —> [nslock unlock] 這中間的代碼塊被鎖定只允許一個線程操作,等到循環完成之後自動執行解鎖操作;這裏我們可以看出synchronized 和 NSLock的區別

名稱 特點
synchronized 對一個對象加鎖
NSLock 對一段代碼加鎖

2.2 NSConditionLock 條件鎖

條件鎖看到了源碼,我們可以這麼理解:在指定條件下加鎖(lock)和解鎖(unlock)

@interface NSConditionLock : NSObject <NSLocking> {
@private
    void *_priv;
}

- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;

@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);

@end

不難看出它的初始化方法包含有一個條件,這個條件是個自定義的值;到XXX時候加鎖(lock)或解鎖(unlock)這個鎖到底怎麼用了 ?我發現網上都他媽沒有講清楚,而且是純扯犢子 。。。 這個鎖比較難理解,可以說是最難理解的一個鎖了,先上代碼和日誌


- (void)viewDidLoad {
    [super viewDidLoad];
    thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL];
    thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject2) object:NULL];

    nslock = [[NSConditionLock alloc] init];
}

-(void)addObject1{
    NSLog(@"1111111111111111");
    for (int i = 0; i < 10; ++i) {
       [nslock lock]; 
        [nslock unlockWithCondition:i];
        NSLog(@"?????????");
        [NSThread sleepForTimeInterval:1];
    }
}
-(void)addObject2{
    NSLog(@"22222222222222222");
    [nslock lockWhenCondition:2];
    NSLog(@"##############");
    [nslock unlock];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    [thread1 start];
    [thread2 start];
}

我們看下日誌輸出神馬東西。。。
這裏寫圖片描述

我們分析下代碼日誌輸出和代碼:
當兩個線程啓動的時候顯然兩個方法addObject1addObject2都執行了 ,因爲我們可以看到1111111111111111 和 22222222222222222但是,並沒有執行addObject2下面的 NSLog(@”##############”);這個方法,而是直接執行addObject1的方法,說明:
lockWhenCondition沒有向下執行而是也就是上鎖失敗,當條件變成2的時候上鎖成功
NSConditionLock(條件鎖)總結

  • NSConditionLock 是鎖,一旦一個線程獲得鎖,其他線程一定等待
  • [xxxx lock]; 表示 xxx 期待獲得鎖,如果沒有其他線程獲得鎖(不需要判斷內部的condition)那它能執行此行以下代碼,如果已經有其他線程獲得鎖(可能是條件鎖,或者無條件鎖), 則等待,直至其他線程解鎖
  • [xxx lockWhenCondition:A條件]; 表示如果沒有其他線程獲得該鎖,但是該鎖內部的condition不等於A條件,它依然不能獲得鎖,仍然等待。如果內部的condition等於A條件,並且沒有其他線程獲得該鎖,則進入代碼區,同時設置它獲得該鎖,其他任何線程都將等待它代碼的完成,直至它解鎖。
  • [xxx unlockWithCondition:A條件]; 表示釋放鎖,同時把內部的condition設置爲A條件
  • return = [xxx lockWhenCondition:A條件 beforeDate:A時間]; 表示如果被鎖定(沒獲得鎖),並超過該時間則不再阻塞線程。但是注意:返回的值是NO,它沒有改變鎖的狀態, 這個函數的目的在於可以實現兩種狀態下的處理
  • 所謂的condition就是整數,內部通過整數比較條件

2.3 NSRecursiveLock 遞歸鎖

遞歸鎖是一種特殊的NSLock主要用在遞歸裏面,上代碼

- (void)viewDidLoad {
    [super viewDidLoad];
    thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL];
    nslock = [[NSRecursiveLock alloc] init];
}

-(void)addObject1{
    [self sumTotal:20];
}
-(void)sumTotal:(long)value{

   [nslock lock];
    if(value >0 ){
        value -- ;
        NSLog(@"%d",value);
        [self sumTotal:value];
    }
    [nslock unlock];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
    [thread1 start];
}

如果不用NSRecursiveLock會怎麼樣了 ?也就是把上面的NSRecursiveLock換成NSLock,來張日誌截圖吧。。。
這裏寫圖片描述

2.4 NSCondition 斷言

這應該是跟條件鎖一樣很操蛋的鎖,因爲這個鎖比較特殊,看看它的方法就覺得它奇葩了

@interface NSCondition : NSObject <NSLocking> {
@private
    void *_priv;
}
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end

wait:等待線程
signal:喚醒一個指定線程
broadcast:喚醒所有線程

我們可以通過這種比較簡單的描述來理解下 ,老套路,上代碼;比較老掉牙的消費者和生產者

- (void)viewDidLoad {
    [super viewDidLoad];

    arrList = [[NSMutableArray alloc] init];
    thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject1) object:NULL];
    thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(addObject2) object:NULL];

    nslock = [[NSCondition alloc] init];

    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 10, 100, 100)];
    button.backgroundColor = [UIColor redColor];
    [button addTarget:self action:@selector(button1) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];

    button = [[UIButton alloc] initWithFrame:CGRectMake(10, 210, 100, 100)];
    button.backgroundColor = [UIColor greenColor];
    [button addTarget:self action:@selector(button2) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

-(void)button1{
    [thread1 start];
}
-(void)button2{
    [thread2 start];
}
-(void)addObject1{
    [nslock lock];
    while ([arrList count] == 0) {
        NSLog(@"等待生產者 生產 !!!!");
        [nslock wait];
    }
    [arrList removeObjectAtIndex:0];
    NSLog(@"消費者消費了一個 產品");
    [nslock unlock];
}
-(void)addObject2{
    [nslock lock];
    [arrList addObject:@"我是一個產品"];
    NSLog(@"產生了一個產品");
    [nslock signal];
    [nslock unlock];
}

先啓動消費者,消費者會等待生產者生產,來個日誌
這裏寫圖片描述

OC中的鎖就介紹到這裏,當然還去其他的C寫的鎖;這裏就不討論了 。。。

發佈了235 篇原創文章 · 獲贊 58 · 訪問量 49萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章