1.线程安全定义:
百度百科:线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
关键字:数据污染
知乎:“线程安全”也不是指线程的安全,而是指内存的安全。对应的解决方法:1.数据不可见,作用域,局部变量,别人访问不到 2.数据隔离,你要拿数据,只能copy一份走,不能拿走原始数据 3.只读标记,只能读,不能改,只能看不能摸。 4.互斥锁,谁先占领,就是谁来改 5.乐观锁,并发机会少,场景记录与验证,大不了从头再来。
CSDN: 没有绝对的安全,只有相对的安全。相对线程安全,牵扯到一些同步调度处理的问题
2.原子操作的定义:一个不能再被分割的计算机操作,中间不能插入别的操作的操作。
3.线程锁的定义:表示对资源的占用标志,上了锁,就保证了内存资源的一种占用状态。
参考这篇文章介绍:
atomic 只是把读与写变成了原子级操作。
nonatomic 是非原子级操作,跟线程安全一点关系没有。
文章中的英文翻译出来就是:
原子的(默认)
原子是默认设置:如果您不输入任何内容,则您的属性是原子的。
原子属性可以保证,如果您尝试从中读取内容,则将取回有效值。
它不能保证该值是多少,但是您将获得合法有效的数据,而不仅仅是垃圾内存(又叫脏数据)。
这允许您执行的操作是,如果您有多个线程或多个进程指向一个变量,则一个线程可以读取而另一个线程可以写入。
如果它们同时命中,则保证读取器线程获得两个值之一:更改之前或更改之后。
原子不会给您带来任何保证,您可能会获得其中哪些值。
原子通常确实与线程安全混淆,这是不正确的。
您需要以其他方式保证线程安全。但是,atomic可以保证,如果您尝试读取数据,则肯定会获得某种合法数据。
非原子
另一方面,您可能会猜到,非原子意味着“不要做原子的事情”。
您所失去的是保证您总是能得到一些回报。
如果尝试在写入过程中进行读取,则可能会获取垃圾数据。
但是,另一方面,您走得更快。
因为原子属性必须做一些魔术(递归锁)才能保证您将获得一个值,所以它们要慢一些。
如果这是您经常访问的属性,则可能需要降低为非原子属性,以确保不会造成速度损失。
nonatomic对象setter和getter方法的实现:
- (void)setCurrentImage:(UIImage *)currentImage
{
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
- (UIImage *)currentImage
{
return _currentImage;
}
atomic对象setter和getter方法的实现:
- (void)setCurrentImage:(UIImage *)currentImage
{
@synchronized(self) {
if (_currentImage != currentImage) {
[_currentImage release];
_currentImage = [currentImage retain];
}
}
}
- (UIImage *)currentImage
{
@synchronized(self) {
return _currentImage;
}
}
现在我来举例:
假如有三个异步线程 asyncThreadA,asyncThreadB,asyncThreadC 对一个变量ticketCount 进行操作(注意:这里使用车站售票模型):
asyncThreadA 先读入票数,再卖出一张票 (读操作是异步,写操作也是异步)
asyncThreadB 先读入票数,再卖出一张票
asyncThreadC 先读入票数,再卖出一张票
假设ticketCount票数初始数为10,读是原子操作,写是原子操作,
那么这三个线程执行完了,输出票数是多少? 答案是:可能是9,可能是8,可能是7.
9的操作顺序可能是: A读B读C读,A写,B写,C写
8的操作顺序可能是: A读B读A写C读,B写,C写
7的操作顺序可能是: A读A写B读B写C读C写。
那么问题来:如果只有一个线程去读去写,是不是就保证线程安全了呢?
也不对,只有一个线程在执行时:也可能发生A读B读C读,A写,B写,C写的操作。
那么怎么保证线程安全呢: 对买票操作加锁,这个读写对(一读一写)操作整体一起加锁。
顺序只能X读X写Y读Y写。
又有一个问题: 多线程 + 串行队列,可以保证线程安全吗?
可以,但是必须将这个读写操作对,当成一个整体任务放入队列中,这样才能保证线程安全。
总而言之:单独一个线程,或者仅仅是串行队列,都不能完全保证线程安全。
单线程+ 串行队列 也不能完全保证实现线程安全。
必须加同步限制。
同步操作怎么实现:
main(){
operationA();
operationB();
}
这个就是表示同步操作:A代码执行完,才去执行B。
实际场景: thread1 读取数据,thread2 使用数据,thread3 写入数据。
如果thread1 读取数据后给thread2发通知,thread2拿到数据,使用数据,在thread2使用数据的时候,thread3 在写入数据,这个时候如果数据都指向同一块内存区域,那么极大可能会读入脏数据。
如果thread1 读取数据后给thread2发通知,thread2拿到数据,使用数据,在thread2使用数据的时候,thread1 又在写入数据,这个时候如果数据都指向同一块内存区域,那么极大可能会读入脏数据。
如果thread1 读取数据后给thread2发通知,thread2拿到数据,使用数据,在thread2使用数据的时候,thread1 与 thread3都没有资格对数据进行修改,数据就安全了。