前言
java代码在执行的时候,是会发生指令重排序的。但是这种指令顺序的改变是不会影响代码间的相互依赖的先后顺序的。不能说你指令重排了,我结果就不可预测了不能xjb改,要遵循一定的规则。那对我们程序员来说依据什么来判断代码的排序规则呢?
这样就有了as-if-serial语义与happen-before原则,规定了一组规范,让我们的代码执行有据可依。
as-if-serial语义
as-if-serial语义的意思是:不管怎么重排序(编译器和处理器为了提高并行度),(单线 程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序,因为 这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译 器和处理器重排序。
举例说明:
1 double pi = 3.14; // A
2 double r = 1.0; // B
3 double area = pi * r * r; // C
A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。因此在最终执行的指令序 列中,C不能被重排序到A和B的前面(C排到A和B的前面,程序的结果将会被改变)。但A和B之 间没有数据依赖关系,编译器和处理器可以重排序A和B之间的执行顺序。
happens-before 先行发生原则
什么是 happens-before 先行发生原则?
- 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作 可见,而且第一个操作的执行顺序排在第二个操作之前。
「这个是JMM内存模型的规定,我们作为开发人员可以完全信任这个规则。但是实际上会发生顺序的改变,请看第二个规则」
- 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
「这个规则说明,JMM允许发生顺序的。但是并不影响最终的结果」
happens-before 具体有哪些规则呢?
-
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;「单线程代码执行的结果书写顺序执行预测,不影响结果的前提可以冲排序。多线程下该规则失效」
-
锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作;「先上锁才能解锁吧?我代码里面是先上的锁,再解锁。优化不至于先解锁吧?」
-
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;「这个有点复杂,下面我详细介绍」
-
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;「如果A>B,B>C 则 A>C 😁」
-
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;「粮草未动,兵马不行」
-
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
-
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
happens-before原则是JMM中非常重要的原则,它是判断数据是否存在竞争、线程是否安全的 主要依据,保证了多线程环境下的可见性。
Volatile
Volatile 读写与内存关系
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
- 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中 读取共享变量。
volatile 读写对重排序影响
| 第一个操作| 第二个操作:普通读写 | 第二个操作:volatile读 |第二个操作:volatile 写| | --- | --- |--- ||--- | | 普通读写 | 可以重排序✅ | 可以重排序✅|不可以重排序❌ | | volatile写 | 可以重排序✅ | 不可以重排序❌|不可以重排序❌ | | volatile读 | 不可以重排序❌ | 不可以重排序❌|不可以重排序❌ |