- 摘要:多线程编程中经常会碰到多个线程访问一个变量的问题,那么我们先来熟悉下我们跟线程相关的修饰符nonatomic和atomic一搜索,会有很多文章;但是这些文章有一个共同的特点那就是nonatomic多线程不安全和atomic多线程安全如何来判断线程安全或不安全?对于小公司在大多数项目说的简单点安全就是不报错,不安全就是报错我写了个demo验证了下@property(strong,nonatomic)NSMutableArray*arrList1;@property(strong
-
多线程编程中经常会碰到多个线程访问一个变量的问题,那么我们先来熟悉下我们跟线程相关的修饰符nonatomic和atomic一搜索,会有很多文章;但是这些文章有一个共同的特点那就是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 ;然后使用这个锁,上代码
也就是 从 [nslock lock] —> [nslock unlock] 这中间的代码块被锁定只允许一个线程操作,等到循环完成之后自动执行解锁操作;这里我们可以看出synchronized 和 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]; }
名称 特点 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]; }
我们看下日志输出神马东西。。。
我们分析下代码日志输出和代码:
当两个线程启动的时候显然两个方法addObject1和addObject2都执行了 ,因为我们可以看到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写的锁;这里就不讨论了 。。。
- 以上是ios中的锁(Lock) NSLock,NSConditionLock,NSRecursiveLock,NSCondition的内容,更多 NSConditionLockNSRecursiveLock NSCondition NSLock lock iOS
iOS 中的NSLock、 NSRecursiveLock、 NSCondition
最新推荐文章于 2021-08-25 17:38:17 发布