似懂非懂的东西,如果别人问你为什么,你说不出个所以然来,最好还是花些时间来搞懂它。不要觉得现在没人问你,相信我,这些坑早晚有一天会踩到,踩到的时候,失去的可能不仅仅是一个机会了。
什么是线程安全???
线程安全:多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。
我们都知道 atomic 原子性 和 nonatomic非原子性
atomic :系统自动生成的getter/setter方法会进行加锁操作;可以理解过读写锁,可以保证读写安全;较耗时;
nonatomic : 系统自动生成的getter/setter方法不会进行加锁操作;但速度会更快;
对于这两种的生成方式,我觉得看代码会更加直观。
@property (nonatomic) UIImage *nonImage;
@property (atomic) UIImage *atomicImage;
//nonatomic的setter和getter实现:
- (void)setNonImage:(UIImage *)nonImage
{
_nonImage = nonImage;
}
- (UIImage *)nonImage
{
return _nonImage;
}
//atomic的setter和getter实现:
- (void)setAtomicImage:(UIImage *)atomicImage
{
@synchronized (self) {
_atomicImage = atomicImage;
}
}
- (UIImage *)atomicImage
{
@synchronized (self) {
return _atomicImage;
}
那么atomic真的是线程安全的吗?
我们为什么要把atomic和线程安全放到一起去探究呢?
我们在代码中可以看到,我们对他的set get方法进行了加锁操作。但是这样紧紧只是保证了读写线程安全,但是真正的线程安全还要包含其他的操作,比如release。如果在当前线程访问另一个线程已经release的变量,相信我可能会crash掉的,那么很明显,atomic并不能真的保证线程安全。
nonatomic MRC下的set 方法
-(void)setName:(NSString *)name{
if (_name != name) {
[_name release];
[name retain];
_name = name;
}
}
下面来看这么一段代码
- (void)atomic{
self.number = 0;
dispatch_apply(10000, dispatch_get_global_queue(0, 0), ^(size_t index) {
self.number ++;
});
NSLog(@"_number:%d", self.number);
}
你觉得打印出来self.number是什么呢?是10000吗?还是其他数。
答案是其他数。
number++等价于
int temp = _number+1;
_number = temp;
虽然atomic保证了number属性线程安全了,但是并不能保证temp变量的线程安全,又因为是多线程的,所以有可能同时执行多次 int temp = _number+1;才执行一次 _number = temp;导致结果每次都不同,而且结果不可预知。
然后我们来看一下atomic的源码
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
if (offset == 0) {
return object_getClass(self);
}
// Retain release world
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
很明显atomic属性的setter/getter方法都被加了spinlock自旋锁,需要注意的是spinlock已经由于存在优先级反转问题被弃用并用os_unfair_lock替代。而实际上也真的已经被替换。
什么是线程安全呢?
线程安全:多线程操作共享数据不会出现想不到的结果就是线程安全的,否则,是线程不安全的。
那么最后来总结下
nonatomic肯定不是线程安全的
atomic修饰后,我们为这个对象加了锁, 不会出现多线程同时修改这个值的。至于这个值最终是什么,无法确定,是因为你不知道多线程的调用顺序,也就无法判断最终的值是什么。
Atomic不能保证对象多线程的安全,它只是能保证你访问的时候给你返回一个完好无损的Value而已。atomic:系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。getter 还是能得到一个完好无损的对象(可以保证数据的完整性),但这个对象在多线程的情况下是不能确定的
如果有多个线程同时调用setter的话,不会出现某一个线程执行完setter全部语句之前,另一个线程开始执行setter情况,相当于函数头尾加了锁一样,每次只能有一个线程调用对象的setter方法,所以可以保证数据的完整性。
atomic所说的线程安全只是保证了getter和setter存取方法的线程安全,并不能保证整个对象是线程安全的。仅仅使用atomic并不会使得对象线程安全,我们还要为对象线程添加lock来确保线程的安全。
网友评论