下面模擬一個賣電影票的場景,從而引出我們今天的話題
// ViewController.m
// 多線程數據安全問題解決
//
// Created by yuxuan on 16/1/23.
// Copyright ? 2016年 apple. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
//記錄當前電影票數量
@property(nonatomic , assign) int totalTicket;
@end
@implementation ViewController
/*
場景說明:
1>默認有100張電影票,賣完停止
2>兩個售票員同時賣票(也就是同時開啟兩個線程來執行賣票操作)
*/
- (void)viewDidLoad {
[super viewDidLoad];
//初始化票數
self.totalTicket = 100;
}
//點擊屏幕開始賣票
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創建售票員1
/*
第一個參數: 目標對象
第二個參數: 調用目標對象的那個方法
*/
NSThread * threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
//設置線程名稱
threadOne.name = @"售票員1";
//創建售票員2
NSThread * threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
threadTwo.name = @"售票員2";
//啟動線程
[threadOne start];
[threadTwo start];
}
//售票方法
-(void)sellTicket{
//獲取當前線程
NSThread * currentThread = [NSThread currentThread];
//一直賣票,直到票數為0
while(1){
int total = self.totalTicket;
if(self.totalTicket > 0){
//讓當前線程睡眠0.1秒
[NSThread sleepForTimeInterval:0.1];
//打印賣票日志
NSLog(@"%@賣出一張票,票號為%d",currentThread,self.totalTicket);
//票數減一
self.totalTicket = --total;
}else{
NSLog(@"票賣完了.%@",currentThread);
break;
}
}
}
@end
-
打印結果:
打印日志.png
看到打印結果,相信大家已經看到問題了,同一張電影票被重復售出.沒錯,這是因為多個線程訪問同一塊數據資源導致的問題.
-
多線程安全隱患
- 同一塊數據資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊數據資源
- 比如說多個線程訪問同一個對象、同一個變量、同一個文件
- 當多個線程訪問同一塊資源時,很容易引發數據錯亂和數據安全問題
- 同一塊數據資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊數據資源
-
思考怎樣才能讓同一塊數據資源在同一時間內只能被一個線程訪問?
- 打個比方:
- 平時我們上廁所,是怎么防止咱們在上廁所的時候突然有人沖了進了?沒錯,把廁所門鎖住,不就可以防止別人打擾咱們拉臭臭了嗎!!!
- 由此可以看出:只要咱們在訪問一塊資源的時候,給它加上一把鎖,訪問結束后,咱們把鎖解開,不就可以解決上面產生的問題了嗎!
- 打個比方:
安全隱患解決 – 互斥鎖
- 互斥鎖使用格式
- @synchronized(鎖對象) { // 需要鎖定的代碼 }
- 鎖定1份代碼只需用1把鎖,用多把鎖是無效的
- 要想真正的鎖住一段代碼,所有線程必須共用一把鎖,也就是鎖對象必須是同一個,開發中一般使用self
- @synchronized(鎖對象) { // 需要鎖定的代碼 }
- 互斥鎖的優缺點
- 優點:能有效防止因多線程搶奪資源造成的數據安全問題
- 缺點:需要消耗大量的CPU資源
互斥鎖的使用前提:多條線程搶奪同一塊資源
下面咱們來看加上鎖之后的運行結果,鎖加在售票方法里
//
// ViewController.m
// 多線程數據安全問題解決
//
// Created by yuxuan on 16/1/23.
// Copyright ? 2016年 apple. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
//記錄當前電影票數量
@property(nonatomic , assign) int totalTicket;
@end
@implementation ViewController
/*
場景說明:
1>默認有100張電影票,賣完停止
2>兩個售票員同時賣票(也就是同時開啟兩個線程來執行賣票操作)
*/
- (void)viewDidLoad {
[super viewDidLoad];
//初始化票數
self.totalTicket = 100;
}
//點擊屏幕開始賣票
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//創建售票員1
/*
第一個參數: 目標對象
第二個參數: 調用目標對象的那個方法
*/
NSThread * threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
//設置線程名稱
threadOne.name = @"售票員1";
//創建售票員2
NSThread * threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(sellTicket) object:nil];
threadTwo.name = @"售票員2";
//啟動線程
[threadOne start];
[threadTwo start];
}
//售票方法
-(void)sellTicket{
//獲取當前線程
NSThread * currentThread = [NSThread currentThread];
//一直賣票,直到票數為0
while(1){
//加上互斥鎖
@synchronized(self) {
int total = self.totalTicket;
if(self.totalTicket > 0){
//讓當前線程睡眠0.1秒
[NSThread sleepForTimeInterval:0.1];
//打印賣票日志
NSLog(@"%@賣出一張票,票號為%d",currentThread,self.totalTicket);
//票數減一
self.totalTicket = --total;
}else{
NSLog(@"票賣完了.%@",currentThread);
break;
}
}
}
}
@end
Paste_Image.png