多線程的安全隱患
資源共享
一塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源,比如多個線程訪問同一個對象、同一個變量、同一個文件,當多個線程訪問同一塊資源時,很容易引發數據錯亂和數據安全問題。
有問題的代碼:
@interface ViewController ()
//剩余票數
@property(nonatomic,assign) int leftTicketsCount;
@property(nonatomic,strong)NSThread *thread1;
@property(nonatomic,strong)NSThread *thread2;
@property(nonatomic,strong)NSThread *thread3;
@end
@implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//默認有20張票
self.leftTicketsCount=10;
//開啟多個線程,模擬售票員售票
self.thread1=[[NSThread alloc]initWithTarget:self selector:@selector(sellTickets) object:nil];
self.thread1.name=@"售票員A";
self.thread2=[[NSThread alloc]initWithTarget:self selector:@selector(sellTickets) object:nil];
self.thread2.name=@"售票員B";
self.thread3=[[NSThread alloc]initWithTarget:self selector:@selector(sellTickets) object:nil];
self.thread3.name=@"售票員C";
}
- (void)sellTickets
{
while (1) {
//1.先檢查票數
int count=self.leftTicketsCount;
if (count>0) {
//暫停一段時間
[NSThread sleepForTimeInterval:0.002];
//2.票數-1
self.leftTicketsCount= count-1;
//獲取當前線程
NSThread *current=[NSThread currentThread];
NSLog(@"%@--賣了一張票,還剩余%d張票",current,self.leftTicketsCount);
}else
{
//退出線程
[NSThread exit];
}
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//開啟線程
[self.thread1 start];
[self.thread2 start];
[self.thread3 start];
}
@end
結果是數據錯亂:
1.png
- 安全隱患分析:
2.png
3.png
- 解決辦法,你需要加一把“鎖”,看代碼吧:
// 將上邊的錯誤代碼中的這個方法加以修改就好了就是加了一個@synchronized(self)
- (void)sellTickets
{
while (1) {
@synchronized(self){//只能加一把鎖
//1.先檢查票數
int count=self.leftTicketsCount;
if (count>0) {
//暫停一段時間
[NSThread sleepForTimeInterval:0.002];
//2.票數-1
self.leftTicketsCount= count-1;
//獲取當前線程
NSThread *current=[NSThread currentThread];
NSLog(@"%@--賣了一張票,還剩余%d張票",current,self.leftTicketsCount);
}else
{
//退出線程
[NSThread exit];
}
}
}
}
其他代碼無需更改,再試下是不是就好了!保證每張票都是只被賣出一次。這個“鎖”就是保證數據不發生錯亂的根本保障。
- 互斥鎖的優缺點
優點:能有效防止因多線程搶奪資源造成的數據安全問題
缺點:需要消耗大量的CPU資源 - 互斥鎖的使用前提:多條線程搶奪同一塊資源
相關專業術語:線程同步,多條線程按順序地執行任務
互斥鎖,就是使用了線程同步技術
原子和非原子屬性
- OC在定義屬性時有nonatomic和atomic兩種選擇
atomic:原子屬性,為setter方法加鎖(默認就是atomic)
nonatomic:非原子屬性,不會為setter方法加鎖
atomic加鎖原理
@property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}
原子和非原子屬性的選擇
nonatomic和atomic對比
atomic:線程安全,需要消耗大量的資源
nonatomic:非線程安全,適合內存小的移動設備iOS開發的建議
所有屬性都聲明為nonatomic
盡量避免多線程搶奪同一塊資源
盡量將加鎖、資源搶奪的業務邏輯交給服務器端處理,減小移動客戶端的壓力