atomic 和 nonatomic

1.atomic :

原子性。系统默认的属性修饰词,系统会生成

默认的属性修饰词,按官方文档上说即使从不同的线程通过getter或setter方法去访问属性也能完全的获取到或设置值,就是当线程A执行getter方法的时候(创建调用栈,返回地址,出栈),线程B如果执行setter方法,就必须先等getter 方法完成才能执行。

看Peak大神blog学的了一点:如果读写(load or store)的内存长度小于等于地址总线的长度,那么读写的操作是原子的,一次完成。比如bool,int,long在64位系统下的单次读写都是原子操作,比如int类型长度为4字节,读和写都可以通过一个指令完成,所以理论上读和写操作都是原子的。从访问内存的角度看nonatomic和atomic也并没有什么区别

2.nonatomic :

相对而言,通过nonatomic修饰的属性,并没有做锁的操作,多线程同时进行setter/getter操作,并不能保证得到一个完整的value,所以相对atomic来说nonatomic修饰的属性访问速度更快,而且平时对线程安全我们更倾向于使用信号量、NSLock和synchronized去控制线程安全,他们都能保证代码块的原子性,所以几乎所有的属性都用nonatomic去修饰。

#是不是使用了atomic就一定多线程安全呢?我们可以看看如下代码
@property (atomic, strong) NSString* stringA; //声明一个原子性的字符串
//线程1
NSThread * thread1 = [[NSThread alloc] initWithBlock:^{
for (int i = 0; i < 100000; i ++) {
if (i % 2 == 0) {
self.stringA = @”a very long string”;
}
else {
self.stringA = @”string”;
}
NSLog(@”Thread A: %@\n”, self.stringA);
}
}];
//线程2
NSThread * thread2 = [[NSThread alloc] initWithBlock:^{
for (int i = 0; i < 100000; i ++) {
if (self.stringA.length >= 10) {
NSString* subStr = [self.stringA substringWithRange:NSMakeRange(0, 10)];
}
NSLog(@”Thread B: %@\n”, self.stringA);
}
}];

虽然stringA是atomic的property,而且在取substring的时候做了length判断,线程B还是很容易crash,因为在前一刻读length的时候self.stringA = @”a very long string”;,下一刻取substring的时候线程A已经将self.stringA = @”string”;,立即出现out of bounds的Exception,crash,多线程不安全。

文末小节:atomic 只是给setter和getter 加了个锁,atomic只能保证进入getter 和 setter 函数内部时是安全的,一旦出了getter 和 setter ,多线程的安全就只能靠我们自己来保障了。另外atomic由于加锁所以会带来一些性能损耗,所以我们在iOS开发的时候,一般声明property为nonatomic,在需要做多线程安全的场景,我们自己去额外加锁做同步。