锁的类别:互斥锁,递归锁,条件锁,自旋锁等
锁的实现方式:NSLock,NSRecursiveLock, NSConditionLock,@synchronized,GCD的信号量等
下面说一下常用的几种锁:
[email protected]:对象级别所,互斥锁,性能较差不推荐使用
@synchronized(这里添加一个OC对象,一般使用self) {
这里写要加锁的代码
}
@synchronized使用注意点
1.加锁的代码尽量少
2.添加的OC对象必须在多个线程中都是同一对象,下面举一个反例
- (void)viewDidLoad { [super viewDidLoad]; //设置票的数量为5 _tickets = 5; //线程一 NSThread *threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; threadOne.name = @"threadOne"; //线程二 NSThread *threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; //开启线程 [threadOne start]; [threadTwo start]; } - (void)saleTickets { NSObject *object = [[NSObject alloc] init]; while (1) { @synchronized(object) { [NSThread sleepForTimeInterval:1]; if (_tickets > 0) { _tickets--; NSLog(@"剩余票数= %ld",_tickets); } else { NSLog(@"票卖完了"); break; } } } }
结果卖票又出错了,出现这个原因的问题是每个线程都会创建一个object对象,锁后面加的object在不同线程中就不同了;
把@synchronized(object)改成 @synchronized(self)就能得到了正确结果
2.NSLock:互斥锁,
@interface ViewController () { NSLock *mutexLock; } @property (assign, nonatomic)NSInteger tickets; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //创建锁 mutexLock = [[NSLock alloc] init]; //设置票的数量为5 _tickets = 5; //线程一 NSThread *threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; threadOne.name = @"threadOne"; //线程二 NSThread *threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; //开启线程 [threadOne start]; [threadTwo start]; } - (void)saleTickets { while (1) { [NSThread sleepForTimeInterval:1]; //加锁 [mutexLock lock]; if (_tickets > 0) { _tickets--; NSLog(@"剩余票数= %ld",_tickets); } else { NSLog(@"票卖完了"); break; } //解锁 [mutexLock unlock]; } }
NSLock: 使用注意,不能多次调用 lock方法,会造成死锁
3.NSRecursiveLock:递归锁
@interface ViewController () { NSRecursiveLock *rsLock; } @property (assign, nonatomic)NSInteger tickets; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //创建锁递归锁 rsLock = [[NSRecursiveLock alloc] init]; //设置票的数量为5 _tickets = 5; //线程一 NSThread *threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; threadOne.name = @"threadOne"; //线程二 NSThread *threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickets) object:nil]; //开启线程 [threadOne start]; [threadTwo start]; } - (void)saleTickets { while (1) { [NSThread sleepForTimeInterval:1]; //加锁,递归锁可以多次加锁 [rsLock lock]; [rsLock lock]; if (_tickets > 0) { _tickets--; NSLog(@"剩余票数= %ld",_tickets); } else { NSLog(@"票卖完了"); break; } //解锁,只有对应次数解锁,其他线程才能访问。 [rsLock unlock]; [rsLock unlock]; } }
4.NSConditionLock:条件锁
NSConditionLock:条件锁,一个线程获得了锁,其它线程等待。
[xxxx lock]; 表示 xxx 期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition) 那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁),则等待,直至其他线程解锁
[xxx lockWhenCondition:A条件]; 表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。
[xxx unlockWithCondition:A条件]; 表示释放锁,同时把内部的condition设置为A条件
@interface ViewController () { NSConditionLock *_cdtLock; //条件锁 } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //创建条件锁 _cdtLock = [[NSConditionLock alloc] init]; [NSThread detachNewThreadSelector:@selector(conditionLockAction1) toTarget:self withObject:nil]; [NSThread detachNewThreadSelector:@selector(conditionLockAction2) toTarget:self withObject:nil]; } - (void)conditionLockAction1 { //阻塞线程2s [NSThread sleepForTimeInterval:2]; for (NSInteger i = 0; i < 3; i++) { //加锁 [_cdtLock lock]; NSLog(@"i = %li", i); //释放锁,并设置condition属性的值为i [_cdtLock unlockWithCondition:i]; } } - (void)conditionLockAction2 { //当标识为2时同步代码段才能够执行,如果标识为其它数字则当前线程被阻塞。 [_cdtLock lockWhenCondition:2]; NSLog(@"thread2"); [_cdtLock unlock]; }
打印结果:
如果我们把代码中[_cdtLock lockWhenCondition:2]换成[_cdtLock lockWhenCondition:1]则会发现出现如下结果
和我们预想的在i = 1后面打印thread2不符合,这是因为conditionLockAction1中的代码段也需要获得锁,同时在循环执行过后把condition置成了2,那么conditionLockAction2就再也没机会加锁了,所以不打印thread2。
我们可以靠下面的代码验证
@interface ViewController () { NSConditionLock *_cdtLock; //条件锁 } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //创建条件锁 _cdtLock = [[NSConditionLock alloc] init]; [NSThread detachNewThreadSelector:@selector(conditionLockAction1) toTarget:self withObject:nil]; [NSThread detachNewThreadSelector:@selector(conditionLockAction2) toTarget:self withObject:nil]; } - (void)conditionLockAction1 { //阻塞线程2s [NSThread sleepForTimeInterval:2]; for (NSInteger i = 0; i < 3; i++) { //加锁 [_cdtLock lock]; NSLog(@"i = %li", i); //释放锁,并设置condition属性的值为i [_cdtLock unlockWithCondition:i]; //在i 为 1的时候阻塞线程1s if (i == 1) { [NSThread sleepForTimeInterval:1]; } } } - (void)conditionLockAction2 { //当标识为2时同步代码段才能够执行,如果标识为其它数字则当前线程被阻塞。 [_cdtLock lockWhenCondition:1]; NSLog(@"thread2"); [_cdtLock unlock]; }
现在的结果就和我们预期的一样了
5.NSCondition:可以理解为互斥锁和条件锁的结合
用生产者消费者中的例子可以很好的理解NSCondition
1.生产者要取得锁,然后去生产,生产后将生产的商品放入库房,如果库房满了,则wait,就释放锁,直到其它线程唤醒它去生产,如果没有满,则生产商品后调用signal,可以唤醒在此condition上等待的线程。
2.消费者要取得锁,然后去消费,如果当前没有商品,则wait,释放锁,直到有线程去唤醒它消费,如果有商品,则消费后会通知正在等待的生产者去生产商品。
生产者和消费者的关键是:当库房已满时,生产者等待,不再继续生产商品,当库房已空时,消费者等待,不再继续消费商品,走到库房有商品时,会由生产者通知消费来消费。
- (void)conditionTest { //创建数组存放商品 products = [[NSMutableArray alloc] init]; condition = [[NSCondition alloc] init]; [NSThread detachNewThreadSelector:@selector(createProducter) toTarget:self withObject:nil]; [NSThread detachNewThreadSelector:@selector(createConsumenr) toTarget:self withObject:nil]; } - (void)createConsumenr { while (1) { //模拟消费商品时间,让它比生产慢一点 [NSThread sleepForTimeInterval:arc4random()%10 * 0.1 + 1.5]; [condition lock]; while (products.count == 0) { NSLog(@"商品为0,等待生产"); [condition wait]; } [products removeLastObject]; NSLog(@"消费了一个商品,商品数 = %ld",products.count); [condition signal]; [condition unlock]; } } - (void)createProducter { while (1) { //模拟生产商品时间 [NSThread sleepForTimeInterval:arc4random()%10 * 0.1 + 0.5]; [condition lock]; while (products.count == 5) { NSLog(@"商品满了,等待消费"); [condition wait]; } [products addObject:[[NSObject alloc] init]]; NSLog(@"生产了一个商品,商品数%ld",products.count); [condition signal]; [condition unlock]; } }
另外发一张总结的图片