Java内存模型(Java Memory Model,简称JMM)是围绕着在并发过程中如何处理原子性,可见性和有序性这三个特征来建立的;
其中关于JMM中的有序性这一特性的处理,在《深入理解Java虚拟机》12.3.6先行发生原则有这么一段话,如下:
从JDK 5开始,Java使用新的JSR-133内存模型,JSR-133使用happens-before的概念来指定两个操作之间的执行顺序,由于这两个操作可以在一个线程之内,也可以是在不同线程之间;因此,JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证;
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系;
参考:[https://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf]
[https://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html]
伪代码如下:
// 以下操作在线程A中完成 i = 1; // 以下操作在线程B中完成 j = i;
假设线程A中的操作“i = 1”先行发生于线程B的操作”j = 1“,那么我们就可以确定在线程B的操作执行后,变量j的值一定等于1;
根据先行发生原则,线程A的”i = 1“的结果可以被线程B观察到,还有就是线程A结束后,没有其他线程会修改变量i的值;
摘自《Java并发编程的艺术》的3.2.3 程序顺序规则
伪代码如下:
double pi = 3.14; //A double r = 1.0; //B double area = pi * r * r; //C
这个例子存在3个happens-before关系,如下:
A happens-before B B happens-before C A happens-before C
在这三个happens-before关系中,第2个和第3个的对执行结果顺序依赖是必须的,而第1个不是必要的;
注:这里的提到的两个操作可以是在一个线程内,也可以是在不同的线程内;
重排序分为下面两类
JMM对于这两种不同性质的重排序,采取了不同的策略,如下:
as-if-serial:
不管怎么重排序(编译器和处理器为了提高并行度),单线程执行的程序结果不能被改变(as-if-serial语义保护单线程程序);编译器,runtime和处理器都必须遵守as-if-serial语义;
例子如下:
double pi = 3.14; //A double r = 1.0; //B double area = pi * r * r; //C
这个例子存在3个happens-before关系,如下:
A happens-before B B happens-before C A happens-before C
在这三个happens-before关系中,A和C之间存在数据依赖关系,同时B和C之前也存在数据依赖关系,因此在最终执行的指令序列中,C不能被重排序到A和B前面;
对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面(这里的“后面”是指时间上的先后)的读是可见的;
Thread#interrupted静态方法,判断线程是否被中断,并清除当前中断状态,这个方法做了两件事:
Thread#interrupt方法仅仅是设置线程的中断状态为true,不会停止线程;
其中,当对一个线程调用Thread#interrupt方法时,如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,被设置中断标志的线程将继续正常运行,不受影响,Thread#interrupt并不能真正的中断线程,需要被调用的线程自己进行配合才行;如果线程处于被阻塞状态(例如调用sleep,wait,join等方法),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常;
如果代码中两个操作之间的关系不在此列,并无法从以上规则推导出来,则它们就没有顺序性保障,虚拟机可以对它们随意地进行重排序;