鎖可以分為兩大類:自旋鎖(OSSpinLock)和互斥鎖(pthread_mutex)。
相同點(diǎn):
都能保證同一時(shí)間只有一個(gè)線程訪問共享資源。都能保證線程安全毁涉。
不同點(diǎn):
互斥鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了脓斩,線程會(huì)進(jìn)入休眠狀態(tài)等待鎖艺骂。一旦被訪問的資源被解鎖志衣,則等待資源的線程會(huì)被喚醒懂昂。
自旋鎖:如果共享數(shù)據(jù)已經(jīng)有其他線程加鎖了寺谤,線程會(huì)以死循環(huán)的方式等待鎖隙畜,一旦被訪問的資源被解鎖,則等待資源的線程會(huì)立即執(zhí)行诺凡。
自旋鎖的效率高于互斥鎖东揣。
各種鎖性能:
除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高 @synchronized() 是性能最低的
OSSpinLock已經(jīng)廢除就不做介紹了
下面是常見的幾種鎖:
@synchronized()
互斥鎖@synchronized (obj):obj為該鎖的唯一標(biāo)識(shí),標(biāo)識(shí)相同則是同一把鎖腹泌。當(dāng)線程里執(zhí)行obj鎖{}代碼塊時(shí) 相當(dāng)于獲得了obj鎖 這時(shí)其他線程就不能獲得obj鎖
只有當(dāng){}代碼塊執(zhí)行完釋放了obj鎖 其他線程才能獲得這個(gè)鎖 并執(zhí)行{}中鎖住的代碼
@synchronized鎖 里套 @synchronized鎖 是不會(huì)死鎖的 因?yàn)锧synchronized底層實(shí)現(xiàn)使用的就是遞歸鎖(NSRecursiveLock)
#pragma mark - @synchronized的互斥
- (void)twoSynchronized{
/* @synchronized(obj)指令使用的obj為該鎖的唯一標(biāo)識(shí)嘶卧,只有當(dāng)標(biāo)識(shí)相同時(shí),才為滿足互斥
下面兩個(gè)@synchronized鎖的標(biāo)識(shí)一樣 是同一把鎖 當(dāng)?shù)谝粋€(gè)線程獲得鎖后 鎖里面的代碼執(zhí)行完后才釋放鎖
第二個(gè)線程才能獲取到鎖 才能繼續(xù)執(zhí)行鎖里面的內(nèi)容 所以執(zhí)行結(jié)果是:
需要線程同步的操作1 開始
需要線程同步的操作1 結(jié)束
需要線程同步的操作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 結(jié)束");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
@synchronized(obj) {
NSLog(@"需要線程同步的操作2");
}
});
}
NSLock 互斥鎖
NSLock 遵循 NSLocking 協(xié)議凉袱,lock() unlock()方法是NSLocking協(xié)議的方法脸候,就不說了 ,一個(gè)加鎖一個(gè)解鎖 總是成對出現(xiàn) .
NSLock類還增加了- (BOOL)tryLock;和- (BOOL)lockBeforeDate:(NSDate *)limit;方法绑蔫。我們來看下:
tryLock嘗試獲取一個(gè)鎖,但是如果鎖不可用的時(shí)候泵额,它不會(huì)阻塞線程配深,相反,它只是返回NO嫁盲。如果獲取到鎖后要記得unlock解鎖篓叶,這樣其他線程才能獲取鎖 相當(dāng)于[lock lockBeforeDate:[NSDate date]];
lockBeforeDate:方法試圖獲取一個(gè)鎖,但是如果鎖沒有在規(guī)定的時(shí)間內(nèi)被獲得羞秤,它會(huì)讓線程從阻塞狀態(tài)變?yōu)榉亲枞麪顟B(tài)(或者返回NO)缸托。會(huì)等待時(shí)間 如果時(shí)間到了還沒有獲取到鎖則返回NO 如果獲取到了就執(zhí)行下面語句 沒獲取到 且時(shí)間還沒到就會(huì)等待著 也就是線程阻塞 不會(huì)執(zhí)行后面的代碼
看一段代碼
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 結(jié)束");
[lock unlock];
});
// 線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1); //以保證讓線程2的代碼后執(zhí)行
if ([lock tryLock]) {//嘗試獲取鎖,如果獲取不到返回NO瘾蛋,不會(huì)阻塞該線程俐镐,如果獲取到鎖 記得unlock解鎖
NSLog(@"鎖可用的操作");
[lock unlock];
}else{
NSLog(@"鎖不可用的操作");
}
NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];
if ([lock lockBeforeDate:date]) {//嘗試在未來的3s內(nèi)獲取鎖,并阻塞該線程哺哼,如果3s內(nèi)獲取不到恢復(fù)線程, 返回NO,不再阻塞線程
NSLog(@"沒有超時(shí)佩抹,獲得鎖");
[lock unlock];
}else{
NSLog(@"超時(shí)叼风,沒有獲得鎖");
}
});
/**
結(jié)果:
需要線程同步的操作1 開始
鎖不可用的操作
需要線程同步的操作1 結(jié)束
沒有超時(shí),獲得鎖
*/
NSConditionLock -- 條件鎖
@interface NSConditionLock : NSObject <NSLocking> {
@private
void *_priv;
}
- (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER;
@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition; //會(huì)阻塞線程的
- (BOOL)tryLock;//是不會(huì)阻塞線程的
- (BOOL)tryLockWhenCondition:(NSInteger)condition;//是不會(huì)阻塞線程的
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;//會(huì)阻塞線程的
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;//會(huì)阻塞線程的
@property (nullable, copy) NSString *name API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
我們發(fā)現(xiàn)條件condition是NSInteger類型的
當(dāng)condition相等的時(shí)候才能獲得鎖 也就是加鎖
需要注意的是- (void)unlockWithCondition:(NSInteger)condition;
這個(gè)方法有兩個(gè)作用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的代碼后執(zhí)行
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的代碼后執(zhí)行
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的代碼后執(zhí)行
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 (滿足了條件,所以先執(zhí)行了線程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
從結(jié)果看 NSConditionLock 還可以實(shí)現(xiàn)任務(wù)之間的依賴
NSRecursiveLock--遞歸鎖
允許同一線程多次加鎖孽鸡,而不會(huì)造成死鎖
看下面的代碼:
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);
// 遞歸調(diào)用
RecursiveMethod(value - 1);
}
[lock unlock];
};
RecursiveMethod(5);
});
這段代碼是一個(gè)典型的死鎖情況。在我們的線程中栏豺,RecursiveMethod是遞歸調(diào)用的彬碱。所以每次進(jìn)入這個(gè)block時(shí),都會(huì)去加一次鎖冰悠,而從第二次開始堡妒,由于鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除溉卓,這樣就導(dǎo)致了死鎖皮迟,線程被阻塞住了。
遞歸鎖允許同一線程多次加鎖桑寨,而不會(huì)造成死鎖伏尼,就是用來解決這類遞歸或者循環(huán)加鎖問題的
同樣的NSRecursiveLock也有兩個(gè)方法 :
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
是跟NSLock一樣的用法
pthread_mutex 互斥鎖
和NSLock
#import <pthread/pthread.h>
#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(@"任務(wù)1開始");
sleep(2);
NSLog(@"任務(wù)1結(jié)束");
Unlock();
});
//線程2
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
sleep(1);
Lock();
NSLog(@"任務(wù)2");
Unlock();
});
}
1、申明一個(gè)互斥鎖尉尾,pthread_mutex_t _lock;
2爆阶、初始化它,pthread_mutex_init(& _lock,NULL);
3沙咏、使用_lock之前一定要初始化辨图,否則不生效
4、獲得鎖肢藐,pthread_mutex_lock(& _lock);
5故河、解鎖,pthread_mutex_unlock(& _lock);
dispatch_semaphore信號(hào)量作為鎖
/* dispatch_semaphore 只有三個(gè)方法
dispatch_semaphore_create(long value); // 創(chuàng)建信號(hào)量 參數(shù)是信號(hào)數(shù)量 必須要大于等于0
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); // 等待獲取信號(hào) 當(dāng)前信號(hào)量大于等于1的時(shí)候 才能獲得 獲得之后信號(hào)量減1
dispatch_semaphore_signal(dispatch_semaphore_t dsema); // 釋放信號(hào)量 使信號(hào)量加1
信號(hào)量相當(dāng)于停車場 有n個(gè)車位 wait 相當(dāng)于來了一輛車 如果有空閑車位 就停進(jìn)去 空閑車位數(shù)減一吆豹,沒有就等待 signal相當(dāng)于開走一輛 空閑車位數(shù)加一
在訪問共同資源時(shí)鱼的,可以控制有幾個(gè)線程來同時(shí)訪問 當(dāng)信號(hào)量為1時(shí) 就相當(dāng)于鎖的作用
*/
@interface LJSemaphoreVC ()
{
dispatch_semaphore_t sem;
}
/** 售票員01 */
@property (nonatomic, strong) NSThread *thread01;
/** 售票員02 */
@property (nonatomic, strong) NSThread *thread02;
/** 售票員03 */
@property (nonatomic, strong) NSThread *thread03;
/** 票的總數(shù) */
@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); // 信號(hào)量減1 如果信號(hào)量 > 0 獲得信號(hào) 執(zhí)行下面代碼 否則等待
// 先取出總數(shù)
NSInteger count = self.ticketCount;
if (count > 0) {
self.ticketCount = count - 1;
NSLog(@"%@賣了一張票,還剩下%zd張", [NSThread currentThread].name, self.ticketCount);
}else{
NSLog(@"票賣完了");
break; // 跳出循環(huán)
}
dispatch_semaphore_signal(sem); // 操作完成 信號(hào)量加1 釋放信號(hào)
}
}
還有讀寫鎖pthread_rwlock痘煤、條件鎖NSCondition等就不一一介紹了凑阶,主要是理解鎖的機(jī)制和原理,能夠知其所以然衷快。