锁可以分为两大类:自旋锁(OSSpinLock)和互斥锁(pthread_mutex)。
相同点:
都能保证同一时间只有一个线程访问共享资源。都能保证线程安全。
不同点:
互斥锁:如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。
自旋锁:如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。
自旋锁的效率高于互斥锁。
各种锁性能:
除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高 @synchronized() 是性能最低的
OSSpinLock已经废除就不做介绍了
下面是常见的几种锁:
@synchronized()
互斥锁@synchronized (obj):obj为该锁的唯一标识,标识相同则是同一把锁。当线程里执行obj锁{}代码块时 相当于获得了obj锁 这时其他线程就不能获得obj锁
只有当{}代码块执行完释放了obj锁 其他线程才能获得这个锁 并执行{}中锁住的代码
@synchronized锁 里套 @synchronized锁 是不会死锁的 因为@synchronized底层实现使用的就是递归锁(NSRecursiveLock)
#pragma mark - @synchronized的互斥
- (void)twoSynchronized{
/* @synchronized(obj)指令使用的obj为该锁的唯一标识,只有当标识相同时,才为满足互斥
下面两个@synchronized锁的标识一样 是同一把锁 当第一个线程获得锁后 锁里面的代码执行完后才释放锁
第二个线程才能获取到锁 才能继续执行锁里面的内容 所以执行结果是:
需要线程同步的操作1 开始
需要线程同步的操作1 结束
需要线程同步的操作2
*/
NSObject *obj = [[NSObject alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@synchronized(obj) {
NSLog(@"需要线程同步的操作1 开始");
sleep(3);
NSLog(@"需要线程同步的操作1 结束");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
@synchronized(obj) {
NSLog(@"需要线程同步的操作2");
}
});
}
NSLock 互斥锁
NSLock 遵循 NSLocking 协议,lock() unlock()方法是NSLocking协议的方法,就不说了 ,一个加锁一个解锁 总是成对出现 .
NSLock类还增加了- (BOOL)tryLock;和- (BOOL)lockBeforeDate:(NSDate *)limit;方法。我们来看下:
tryLock尝试获取一个锁,但是如果锁不可用的时候,它不会阻塞线程,相反,它只是返回NO。如果获取到锁后要记得unlock解锁,这样其他线程才能获取锁 相当于[lock lockBeforeDate:[NSDate date]];
lockBeforeDate:方法试图获取一个锁,但是如果锁没有在规定的时间内被获得,它会让线程从阻塞状态变为非阻塞状态(或者返回NO)。会等待时间 如果时间到了还没有获取到锁则返回NO 如果获取到了就执行下面语句 没获取到 且时间还没到就会等待着 也就是线程阻塞 不会执行后面的代码
看一段代码
NSLock *lock = [[NSLock alloc] init];
// 线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lockBeforeDate:[NSDate date]];
NSLog(@"需要线程同步的操作1 开始");
sleep(2);
NSLog(@"需要线程同步的操作1 结束");
[lock unlock];
});
// 线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1); //以保证让线程2的代码后执行
if ([lock tryLock]) {//尝试获取锁,如果获取不到返回NO,不会阻塞该线程,如果获取到锁 记得unlock解锁
NSLog(@"锁可用的操作");
[lock unlock];
}else{
NSLog(@"锁不可用的操作");
}
NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];
if ([lock lockBeforeDate:date]) {//尝试在未来的3s内获取锁,并阻塞该线程,如果3s内获取不到恢复线程, 返回NO,不再阻塞线程
NSLog(@"没有超时,获得锁");
[lock unlock];
}else{
NSLog(@"超时,没有获得锁");
}
});
/**
结果:
需要线程同步的操作1 开始
锁不可用的操作
需要线程同步的操作1 结束
没有超时,获得锁
*/
NSConditionLock -- 条件锁
@interface NSConditionLock : NSObject {
@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 API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
我们发现条件condition是NSInteger类型的
当condition相等的时候才能获得锁 也就是加锁
需要注意的是- (void)unlockWithCondition:(NSInteger)condition;
这个方法有两个作用1、就是解锁 2、是改变锁的条件
看下面代码:
//主线程中
NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0];
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[lock lockWhenCondition:1];
NSLog(@"线程1");
sleep(2);
[lock unlock];
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);//以保证让线程2的代码后执行
if ([lock tryLockWhenCondition:0]) {
NSLog(@"线程2");
[lock unlockWithCondition:2];
NSLog(@"线程2解锁成功");
} else {
NSLog(@"线程2尝试加锁失败");
}
});
//线程3
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);//以保证让线程2的代码后执行
if ([lock tryLockWhenCondition:2]) {
NSLog(@"线程3");
[lock unlock];
NSLog(@"线程3解锁成功");
} else {
NSLog(@"线程3尝试加锁失败");
}
});
//线程4
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(3);//以保证让线程2的代码后执行
if ([lock tryLockWhenCondition:2]) {
NSLog(@"线程4");
[lock unlockWithCondition:1];
NSLog(@"线程4解锁成功");
} else {
NSLog(@"线程4尝试加锁失败");
}
});
2019-02-21 10:47:29.227968+0800 LJConditionLock[2191:320996] 线程2 (满足了条件,所以先执行了线程2 ,然后改变条件为2)
2019-02-21 10:47:29.228109+0800 LJConditionLock[2191:320996] 线程2解锁成功
2019-02-21 10:47:30.227654+0800 LJConditionLock[2191:320995] 线程3 (线程2改变条件为2 正好等于线程3的条件)
2019-02-21 10:47:30.227843+0800 LJConditionLock[2191:320995] 线程3解锁成功
2019-02-21 10:47:31.226466+0800 LJConditionLock[2191:320997] 线程4
2019-02-21 10:47:31.226634+0800 LJConditionLock[2191:320997] 线程4解锁成功
2019-02-21 10:47:31.226645+0800 LJConditionLock[2191:320994] 线程1
从结果看 NSConditionLock 还可以实现任务之间的依赖
NSRecursiveLock--递归锁
允许同一线程多次加锁,而不会造成死锁
看下面的代码:
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
RecursiveMethod = ^(int value) {
[lock lock];
if (value > 0) {
NSLog(@"value = %d", value);
sleep(2);
// 递归调用
RecursiveMethod(value - 1);
}
[lock unlock];
};
RecursiveMethod(5);
});
这段代码是一个典型的死锁情况。在我们的线程中,RecursiveMethod是递归调用的。所以每次进入这个block时,都会去加一次锁,而从第二次开始,由于锁已经被使用了且没有解锁,所以它需要等待锁被解除,这样就导致了死锁,线程被阻塞住了。
递归锁允许同一线程多次加锁,而不会造成死锁,就是用来解决这类递归或者循环加锁问题的
同样的NSRecursiveLock也有两个方法 :
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
是跟NSLock一样的用法
pthread_mutex 互斥锁
和NSLock
#import
#define Lock() pthread_mutex_lock(&_lock)
#define Unlock() pthread_mutex_unlock(&_lock)
@implementation LJpthread_mutexVC
{
pthread_mutex_t _lock; // 声明
}
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化
pthread_mutex_init(&_lock,NULL);
//线程1
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
Lock();
NSLog(@"任务1开始");
sleep(2);
NSLog(@"任务1结束");
Unlock();
});
//线程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
sleep(1);
Lock();
NSLog(@"任务2");
Unlock();
});
}
1、申明一个互斥锁,pthread_mutex_t _lock;
2、初始化它,pthread_mutex_init(& _lock,NULL);
3、使用_lock之前一定要初始化,否则不生效
4、获得锁,pthread_mutex_lock(& _lock);
5、解锁,pthread_mutex_unlock(& _lock);
dispatch_semaphore信号量作为锁
/* dispatch_semaphore 只有三个方法
dispatch_semaphore_create(long value); // 创建信号量 参数是信号数量 必须要大于等于0
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等待获取信号 当前信号量大于等于1的时候 才能获得 获得之后信号量减1
dispatch_semaphore_signal(dispatch_semaphore_t dsema); // 释放信号量 使信号量加1
信号量相当于停车场 有n个车位 wait 相当于来了一辆车 如果有空闲车位 就停进去 空闲车位数减一,没有就等待 signal相当于开走一辆 空闲车位数加一
在访问共同资源时,可以控制有几个线程来同时访问 当信号量为1时 就相当于锁的作用
*/
@interface LJSemaphoreVC ()
{
dispatch_semaphore_t sem;
}
/** 售票员01 */
@property (nonatomic, strong) NSThread *thread01;
/** 售票员02 */
@property (nonatomic, strong) NSThread *thread02;
/** 售票员03 */
@property (nonatomic, strong) NSThread *thread03;
/** 票的总数 */
@property (nonatomic, assign) NSInteger ticketCount;
@end
@implementation LJSemaphoreVC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
sem = dispatch_semaphore_create(1);
self.ticketCount = 100;
self.thread01 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread01.name = @"售票员01";
self.thread02 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread02.name = @"售票员02";
self.thread03 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread03.name = @"售票员03";
[self.thread01 start];
// sleep(1);
[self.thread02 start];
[self.thread03 start];
}
- (void)saleTicket
{
while (1) {
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER); // 信号量减1 如果信号量 > 0 获得信号 执行下面代码 否则等待
// 先取出总数
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@卖了一张票,还剩下%zd张", [NSThread currentThread].name, self.ticketCount);
}else{
NSLog(@"票卖完了");
break; // 跳出循环
}
dispatch_semaphore_signal(sem); // 操作完成 信号量加1 释放信号
}
}
还有读写锁pthread_rwlock、条件锁NSCondition等就不一一介绍了,主要是理解锁的机制和原理,能够知其所以然。