因為還是喜歡簡書的界面和簡潔的markdown書寫方式,所以把以前寫的文章從博客園遷移過來,如果大家以前看過,謝謝大家持久以來的關注,還會陸續更新很多iOS學習文章及其筆記。
本文要點
- 多線程安全隱患引出
- 多線程安全隱患代碼示例
- 多線程安全隱患解決方案
一、多線程安全隱患引出
假設火車站有3個賣票窗口,余票是1000,賣票窗口3個線程同一時刻讀取剩余票數,都是讀取的1000,賣票線程1賣了一張 ,余票變成999。賣票線程2反應慢點,在賣票線程1后面執行賣票,因為賣票線程2剛開始讀取的余票也是1000,所以在賣掉一張后,余額也變成999。賣票線程3反應更慢,在賣票線程2后面執行賣票,因為賣票線程3剛開始讀取的余票也是1000,所以在賣掉一張后,余額依舊也變成999。所以出現了錯誤,本來賣了3張,可是余票還有999張。
因此當多個線程訪問同一塊資源時(同一個對象、同一個變量、同一個文件),很容易引發數據錯亂和數據安全問題。
二、多線程安全隱患代碼示例
1>聲明3個賣票線程,以及 leftTicketCount 來保存余票數
@property (nonatomic, strong) NSThread *thread1; //線程1
@property (nonatomic, strong) NSThread *thread2; //線程2
@property (nonatomic, strong) NSThread *thread3; //線程3
@property (nonatomic, assign) int leftTicketCount; //剩余票數
2>在viewDidLoad里面,設置余票為50張票,并創建了3個線程,并且分別起名“1號窗口”,“2號窗口” 及 “3號窗口”
self.leftTicketCount = 50;
self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread1.name = @"1號窗?口";
self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread2.name = @"2號窗?口";
self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread3.name = @"3號窗?口";
3>實現售票方法
- (void)saleTicket {
while (1) {
int count = self.leftTicketCount;
if (count > 0) {
//線程睡?一下,其他線程才能有機可乘
[NSThread sleepForTimeInterval:0.05];
self.leftTicketCount = count - 1;
NSLog(@"%@賣了?一張票, 剩余%d張票", [NSThread currentThread].name, self.leftTicketCount);
}else {
return; // 退出循環
}
}
}
輸出結果為:
從結果可以輕易看出多個線程訪問并操作剩余票數的時候,引發了數據錯亂和數據安全問題
三、多線程安全隱患解決方案
在線程A讀取數據后,加一把鎖,別的線程就不能訪問了,只允許加鎖的線程A訪問。當這個加鎖線程操作完數據后,線程A解鎖,此時別的線程就能訪問了, 假設輪到線程B訪問,線程B也先加把鎖,保證只有自己能訪問,執行完操作再解鎖....就這樣循環往復,只要誰訪問就加把鎖,直到操作結束后再解鎖。這就是互斥鎖。
加鎖的目的就是為了保證同一時間,只能一個線程訪問并執行代碼。
代碼實現互斥鎖
- (void)saleTicket {
while (1) {
// ()?小括號?里?面放的是鎖對象
@synchronized(self) { // 開始加鎖
int count = self.leftTicketCount;
if (count > 0) {
[NSThread sleepForTimeInterval:0.05];
self.leftTicketCount = count - 1;
NSLog(@"%@賣了?一張票, 剩余%d張票", [NSThread currentThread].name, self.leftTicketCount);
} else {
return; // 退出循環
}
} // 解鎖
}
}
通過把自身當做鎖對象進行加鎖,保證了自己在訪問操作數據的時候,別的線程沒法進來,只有等待執行完后開鎖。
注意:
1.@synchronized(self) 不能寫在 while (1)前面,盡管打印結果正確,但是始終執行的只有最先進來的進程。
2.盡管互斥鎖能夠有效防止因多線程搶奪資源而造成的數據安全問題,但是其需要消耗大量的CPU資源。
3.而且只有再多條線程搶奪同一塊資源的時候才使用互斥鎖
聯系方式
如果你喜歡這篇文章,可以繼續關注我,微博:極客湯米,歡迎交流。