AVO原则
1.A就是原子性
对基本数据类型的变量读和写是保证原子性的,要么都成功,要么都失败,这些操作不可中断。
2.V就是可见性
使用volatile关键字,保证了变量的可见性,到主存拿数据,不是到缓存里拿。
3.O就是有序性
代码的执行顺序,在代码编译前的和代码编译后的执行顺序不变。

线程安全—可见性和有序性

在并发编程中,需要处理的两个关键问题:线程之间如何通信以及线程之间如何同步。
通信是指线程之间以或者机制交换信息,java的并发采用的是共享内存模型,线程之间共享程序的公共状态,通过读写内存中的公共状态进行隐式通信。
同步是是指程序中用于控制不同线程间操作发生相对顺序的机制。

JMM

线程安全 - 图1

指令重排序

指令重排序:代码书写的顺序与实际执行的顺序不同,指令重排序是编译器或处理器为了提高程序性能而做的优化。
1.编译器优化的重排序(编译器优化)
2.指令级并行重排序(处理器优化)
3.内存系统的重排序(处理器优化)
不是所有的语句的执行顺序都可以重排呢,存在数据依赖的代码是不能重排序的。
nt num1=1;//第一行
int num2=2;//第二行
int sum=num1+num;//第三行
单线程:第一行和第二行可以重排序,但第三行不行
重排序不会给单线程带来内存可见性问题
多线程中程序交错执行时,重排序可能会照成内存可见性问题。

as-if-serial语义(也叫数据依赖性)

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖。数据依赖分下列三种类型:
名称 代码示例 说明
写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
读后写 a = b;b = 1; 读一个变量之后,再写这个变量。
上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。所以,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。也就是说:在单线程环境下,指令执行的最终效果应当与其在顺序执行下的效果一致,否则这种优化便会失去意义。这句话有个专业术语叫做as-if-serial semantics (as-if-serial语义)

可见性分析

导致共享变量在线程间不可见的原因:
线程的交叉执行
重排序结合线程交叉执行
共享变量更新后的值没有在工作内存与主内存间及时更新

重排序对多线程的影响

  1. class ReorderExample {
  2. int a = 0;
  3. boolean flag = false;
  4. public void writer() {
  5. a = 1; // 1
  6. flag = true; // 2
  7. }
  8. public void reader() {
  9. if (flag) { // 3
  10. int i = a * a; // 4
  11. }
  12. }
  13. }

flag变量是个标记,用来标识变量a是否已被写入。这里假设有两个线程A和B,A首先执行writer()方法,随后B线程接着执行reader()方法。线程B在执行操作4时,能否看到线程A在操作1对共享变量a的写入?
答案是:不一定能看到。
由于操作1和操作2没有数据依赖关系,编译器和处理器可以对这两个操作重排序;同样,操作3和操作4没有数据依赖关系,编译器和处理器也可以对这两个操作重排序。让我们先来看看,当操作1和操作2重排序时,可能会产生什么效果?
执行顺序是:2 -> 3 -> 4 -> 1 (这是完全存在并且合理的一种顺序,如果你不能理解,请先了解CPU是如何对多个线程进行时间分配的)

可见性-synchronized

JMM关于synchronized的两条规定
1.线程解锁前,必须把共享变量的最新值刷到主内存
2.线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量是需要从内存够中重新读取最新的值(注意,枷锁与解锁是同一把锁)
volatile实现可见性
能够保证volatile变量的可见性
只能保证单个volatile变量的原子性,对于volatile++这种复合操作不具有原子性
深入来说:通过加入内存屏障和禁止重排序优化来实现的。

synchronized和volatile的比较

synchronized锁住的是变量和变量的操作,而volatile锁住的只是变量,而且该变量的值不能依赖它本身的值,volatile算是一种轻量级的同步锁
volatile不需要加锁,比synchronized更加轻量级,不会阻塞线程。
从内存可见性角度讲,volatile读相当于加锁,volatilexie相当于解锁。
synchronized既能保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。

有序性

Happens-before原则,先天有序性,即不需要任何额外的代码控制即可保证有序性,java内存模型一个列出了八种Happens-before规则,如果两个操作的次序不能从这八种规则中推倒出来,则不能保证有序性。
1.程序次序规则:一个线程内,按照代码执行,书写在前面的操作先行发生于书写在后面的操作。
2.锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
3.volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
4.传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
5.线程启动原则:Thread对象的start()方法先行发生于此线程的每一个动作
6.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()方法返回值手段检测到线程已经终止执行
8.对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

安全性总结

原子性,Atomic包,CAS算法,synchronized、Lock
可见性,synchronized,volatile
有序性,happens-before