一、atomic介紹
github對應Demo:https://github.com/Master-fd/LockDemo
在iOS中,@property 新增屬性時,可以增加atomic選項,atomic會給對應對setter方法加鎖,相當於
- (void)setTestStr:(NSString *)testStr
{
@synchronizad(lock){
if (testStr != _testStr) {
[_testStr release];
_testStr = [testStr retain];
}
}
}
那么就有問題了,為什么atomic又不是線程安全的呢??而且還會代理性能問題,比起nonatomic性能可能要大減20倍,如果頻繁的調用,可能更多。
1、當線程A,給TestStr設置值得時候,會調用對應的setter方法,也就是加鎖了,此時B線程也要對TestStr進行設置新值,因為A線程已經鎖住了,所以B只能等待,這個時候是線程安全的。
2、當線程A,給TestStr設置值得時候,此時B線程在讀TestStr的值,因為setter和getter方法是沒有聯系的,這時,A在執行到加鎖,只是還沒有設置值,然而B線程已經讀取走了,本來是想讀取A設置之后的值,卻讀取了設置之前的值,也就線程不安全了。
3、當線程A,給TestStr設置值得時候,C線程在A之前release了TestStr,此時就會導致崩潰,也就是線程不安全了。
總的來說,atomic只是保證了setter方法的安全,沒有保證對應成員變量的多線程安全,所以不是真正的線程安全。
二、線程安全的辦法
2.1、synchronizad 給需要加鎖的代碼進行加鎖。
- (IBAction)synchronizad:(id)sender {
FDLog(@"synchronizad 測試");
static NSObject *lock = nil;
if (!lock) {
lock = [[NSString alloc] init];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FDLog(@"線程A,准備好");
@synchronized(lock){
FDLog(@"線程A lock, 請等待");
[NSThread sleepForTimeInterval:3];
FDLog(@"線程A 執行完畢");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FDLog(@"線程B,准備好");
@synchronized(lock){
FDLog(@"線程B lock, 請等待");
[NSThread sleepForTimeInterval:1];
FDLog(@"線程B 執行完畢");
}
});
}
上面的AB線程都使用了同一把鎖,對相應代碼進行加鎖,所以鎖內的代碼是線程安全的。
2.2、NSLook 對多線程需要安全的代碼加鎖
- (IBAction)NSLook:(id)sender {
static NSLock *lock = nil;
if (!lock) {
lock = [[NSLock alloc] init];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FDLog(@"線程A,准備好");
[lock lock];
FDLog(@"線程A lock, 請等待");
[NSThread sleepForTimeInterval:3];
FDLog(@"線程A 執行完畢");
[lock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FDLog(@"線程B,准備好");
[lock lock];
FDLog(@"線程B lock, 請等待");
[NSThread sleepForTimeInterval:1];
FDLog(@"線程B 執行完畢");
[lock unlock];
});
}
上面的AB線程都使用了同一把鎖,對相應代碼進行加鎖,所以鎖內的代碼是線程安全的。
2.3、NSCondition 條件鎖,只有達到條件之后,才會執行鎖操作,否則不會對數據進行加鎖
- (IBAction)NSCondition:(id)sender {
#define kCondition_A 1
#define kCondition_B 2
__block NSUInteger condition = kCondition_B;
static NSConditionLock *conditionLock = nil;
if (!conditionLock) {
conditionLock = [[NSConditionLock alloc] initWithCondition:condition];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FDLog(@"線程A,准備好,檢測是否可以加鎖");
BOOL canLock = [conditionLock tryLockWhenCondition:kCondition_A];
if (canLock) {
FDLog(@"線程A lock, 請等待");
[NSThread sleepForTimeInterval:1];
FDLog(@"線程A 執行完畢");
[conditionLock unlock];
}else{
FDLog(@"線程A 條件不滿足,未加lock");
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FDLog(@"線程B,准備好,檢測是否可以加鎖");
BOOL canLock = [conditionLock tryLockWhenCondition:kCondition_B];
if (canLock) {
FDLog(@"線程B lock, 請等待");
[NSThread sleepForTimeInterval:1];
FDLog(@"線程B 執行完畢");
[conditionLock unlock];
}else{
FDLog(@"線程B 未加lock");
}
});
}
2.4、NSRecursiveLock 遞歸鎖,同一個線程可以多次加鎖,但是不會引起死鎖,如果是NSLock,則會導致崩潰
- (void)reverseDebug:(NSUInteger )num lock:(NSRecursiveLock *)lock
{
[lock lock];
if (num<=0) {
FDLog(@"結束");
return;
}
FDLog(@"加了遞歸鎖, num = %ld", num);
[NSThread sleepForTimeInterval:0.5];
[self reverseDebug:num-1 lock:lock];
[lock unlock];
}
- (IBAction)NSRecursiveLock:(id)sender {
static NSRecursiveLock *lock = nil;
if (!lock) {
lock = [[NSRecursiveLock alloc] init];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self reverseDebug:5 lock:lock];
});
}
github對應Demo:https://github.com/Master-fd/LockDemo
