1. 原子性
操作是原子的,则它不可被分割。从另一个线程的视角来看,它不应该看到这个操作的中间状态,只能看到两种状态:①还没开始执行 ② 已经执行结束。
在多个线程访问临界资源时,如果临界区的代码不是原子的,则一个线程执行到某个中间状态,然后被时钟中断,另一个线程接着执行,就会覆盖数据或访问到不正确的数据。
2. 可见性
由于在存储器层次结构中,更高层的数据是更低层数据的一个子集,该数据副本可能存在存储系统的不同层中。为了提高性能,高层数据写回底层并不是实时的,因此可能造成在某时刻高层数据和底层数据的不一致。
在多核cpu下,由于每个核内部都内置了cache,各个核的cache可能缓存了同一个内存地址的数据,cache之间如果没有某种机制协调,会导致数据不一致(各个线程看到相同数据的值不一样)。
当一个线程修改了共享数据后,我们希望其他线程也能看到这个改变后的结果。如果其他线程看不到这个修改后的数据,继续访问缓存的旧数据,就可能出现问题。
3. 有序性
① 哪些地方可能产生重排序?
源代码-->字节码/机器码-->存储系统-->CPU
会造成重排序的有:① 编译器 ② 存储系统 ③ CPU的执行单元。
② 为什么要重排序呢?
为了提高性能。比如更利于cpu的流水、乱序执行;cpu的异步写入store buffer;store buffer的合并写操作等。
③ 重排序会造成什么影响呢?
比如:
a = 1;
b = 2;
在执行b = 2这行代码时,我们可能想当然的觉得a = 1应该已经执行完了,即我们认为在这个时刻,应该能够看到a的值是1。遗憾的是,由于重排序的存在,这一点是不能保证的。
当然,重排序也不是随便就打乱顺序的(保证As-if-serial语义),单线程下存在依赖关系的代码顺序是不能被打乱的,而在多线程下由于多个线程数据的依赖关系比较复杂,难以预测和分析,因此交给软件开发者来指明,而硬件开发者则需要提供相关指令(如内存屏障)供软件开发者使用。
总结:
只有原子性是不能保证线程安全的。就算操作原子了,其运算的结果不能让另一个线程看到,另一个线程还是读的旧值,那结果还是错误的。
但是就算有了原子性和可见性,也不能保证线程安全。我们本来期望在某行代码处能看到前面代码的运算结果,结果由于重排序,前面代码被重排到后面了。因此在此处我们看到了旧值(可见是可见了,但期望的代码排到后面了还未执行)。
因此要保证线程安全,原子性、可见性和有序性缺一不可。
来源:oschina
链接:https://my.oschina.net/u/2504786/blog/3188989