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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章