这两个关键字面试经常被问到,每次回答的貌似对了,但是又貌似不是特别理解,因此为又研究了一遍,特此做个记录。
结论
nonatimic、atomic读写安全,但效率低,不是绝对的安全,比如操作数组,增加或移除,这种情况可以使用互斥锁来保证线程安全(atomic并不能保证多线程安全,只能保证数据的完整性,完整性是,使用者总能取到完整的值,不存在给已经释放的对象设置值)
atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。
是给set和get方法加了锁
一、使用nonatomic崩溃
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("abcde", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0 ; i < 10000; i ++) {
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]);
self.name = [NSString stringWithFormat:@"我%d",i]; // 会新创建一个新的字符串对象
});
}
}
@end
分析:
属性的set方法,ARC环境下,如果有两个线程同时给属性设置值时,就会同时把属性给release释放两次。过渡释放造成崩溃
属性是atomic时,不会崩溃,里面加了同步锁,set,get方法只能串行执行。
二、nonatimic、atomic不是线程安全的
///
@property (nonatomic, assign) int slice;
self.slice = 0;
NSLog(@"%p",&_slice);
dispatch_queue_t queue = dispatch_queue_create("TestQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i=0; i<10000; i++) {
self.slice = self.slice + 1;
}
});
dispatch_async(queue, ^{
for (int i=0; i<10000; i++) {
self.slice = self.slice + 1;
}
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%p",&self->_slice);
NSLog(@"%d",self.slice);
});
分析
eg1:如果定义属性NSInteger i是原子的,对i进行i = i + 1操作就是不安全的; 因为原子性只能保证读写安全,而该表达式需要三步操作:
1、读取i的值存入寄存器;
2、将i加1;
3、修改i的值;
如果在第一步完成的时候,i被其他线程修改了,那么表达式执行的结果就与预期的不一样,也就是不安全的
结果可能是[10000,20000]之间的某个值,所以atomic是线程不安全的。但是线程安全还有除了读写的其他操作,比如:当一个线程正在get/set时,另一个线程同时进行release操作,可能会直接crash。很明显atomic的读写锁不能保证线程安全。
网友评论