1. atomic 和 nonatomic 的区别
首先,我们看以下代码,第一行是nonatomic修饰的,非原子操作;后两行是一样的,不写的话默认就是atomic
。
@property(nonatomic, strong) UITextField *userName;
@property(atomic, strong) UITextField *userName;
@property(strong) UITextField *userName;
atomic 和 nonatomic 的区别在于,系统自动生成的 getter/setter 方法不一样。如果你自己写 getter/setter,那 atomic/nonatomic/retain/assign/copy 这些关键字只起提示作用,写不写都一样。
对于 atomic 的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。
而 nonatomic 就没有这个保证了。所以,nonatomic 的速度要比 atomic 快。
不过 atomic 只能保证对象级的原子操作,可并不能保证线程安全。如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了 setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set 的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的值。
- Atomic
速度不快,因为要保证操作整体完成;
会保证多线程访问这个属性get、set 方法的原子操作,属性是读/写安全的。
@property(retain) UITextField *userName;
// 系统生成的代码如下:
- (UITextField *) userName {
UITextField *retval = nil;
@synchronized(self) {
retval = [[userName retain] autorelease];
}
return retval;
}
- (void) setUserName:(UITextField *)userName_ {
@synchronized(self) {
[userName release];
userName = [userName_ retain];
}
}
- Non-Atomic
更快,但线程不安全;
若两个线程频繁同时访问同一属性,结果无法预料。
@property(nonatomic, retain) UITextField *userName;
//系统生成的代码如下:
- (UITextField *) userName {
return userName;
}
- (void) setUserName:(UITextField *)userName_ {
[userName_ retain];
[userName release];
userName = userName_;
}
简单来说,就是 atomic 会加一个锁来保障线程安全,并且引用计数会 +1,来向调用者保证这个对象会一直存在。假如不这样做,如有另一个线程调 setter,可能会出现线程竞态,导致引用计数降到0,原来那个对象就释放掉了。
atomic虽然能保证属性是读/写安全的,但是在多线程get、set的同时,如果有另一个线程 D 同时调用 [name release],那可能就会发生crash,因为 release 不受 getter/setter 操作的限制。也就是说,这个属性只能说是读/写安全的,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。如果一个对象的改变不是直接调用 getter/setter 方法,而是直接对对象内部属性修改、字符串拼接、数组增加和移除元素等操作,就不能保证这个对象是线程安全的。线程安全需要开发者自己来保证。
2. 实例一
// nonatomic属性
@property (nonatomic, strong) NSString *name;
// atomic属性
@property (atomic, assign) int number;
对于 name 属性使用 nonatomic 修饰:
// 10000个异步任务,修改name属性的值
- (void)nonatomic{
for (NSInteger i = 0; i < 10000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.name = [NSString stringWithFormat:@"name:%ld", i];
});
}
}
执行结果:崩溃,崩溃原因是在子线程Thread8上,对象释放了。
- 在nonatomic属性name的set方法中有[_name release]操作,多线程进入且没加锁;
- 在多个线程同时走过[_name release],二次release会造成崩溃;
- 将属性的nonatomic改为atomic就不会出现崩溃了。
number属性使用atomic修饰:
- (void)atomic{
self.number = 0;
dispatch_apply(10000, dispatch_get_global_queue(0, 0), ^(size_t index) {
self.number ++;
});
NSLog(@"_number:%d", self.number);
}
执行结果:执行结果并不是10000,而且每次运行结果都不一样,即运行结果不可预见。
在多个线程同时读取了self.number及其值,++后又set回去这一过程中,atomic保证了number属性读、写操作的线程安全,但是并没有保证读/写的整个逻辑过程在多异步线程间的同步。也就是说,多线程间并没有给读/写的整个逻辑过程加锁。
3. 实例二
NSMutableArray 和NSMutableDictionary 都不是线程安全的,主线程我们多次操作 都没有问题,但多线程下短时间内有大量的读写操作会引起数据的错乱导致crash。
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *originArray = [NSMutableArray new];
for (int i = 0 ; i < 1000; i++) {
dispatch_async(quene, ^{
[originArray addObject:[NSString stringWithFormat:@"item%ld", i]];
});
}
这样直接会crash,怎样解决呢?加个 @synchronized 就可以解决:
for (int i = 0 ; i < 1000; i++) {
dispatch_async(quene, ^{
@synchronized (originArray) {
[originArray addObject:[NSString stringWithFormat:@"item%ld", i]];
}
});
}
但是synchronized效率比较低。关于 NSMutableArray 的读写 安全高效的做法是这样的在读的时候我们使用GCD同步机制,写的时候使用GCD的Barrier:
//模仿的写操作
- (void)addItem:(id)item {
dispatch_barrier_async(self.readWriteQuene, ^{
[self.array addObject:item];
});
}
//模仿的读操作
- (id)getLastItem {
__block id item = nil;
dispatch_sync(self.readWriteQuene, ^{
NSUInteger size = self.array.count;
if (size > 0) {
item = self.array[size - 1];
}
});
return item;
}
当然,我们还可以对NSMutableArray加以封装,使其他相关操作都加上GCD同步机制。一般插入、添加、替换、删除等使用dispatch_barrier_async约束,而读取类操作使用dispatch_sync即可,原因不言而喻。
参考文章:
https://www.jianshu.com/p/57ccbf5c704b
https://blog.csdn.net/shifang07/article/details/100576587
https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProperties.html
网友评论