从 JDK 5开始,Java 使用新的 JSR-133 内存模型,使用 happens-before 的概念来阐述操做间的可见性。java
JSR-133 对Happens-Before 的定义:程序员
Happens-Before Relationship Two actions can be ordered by a happens-before relationship. If one action > happens-before another, then the first is visible to and ordered before the second. It should be stressed that a happens-before relationship between two actions does not imply that those actions must occur in that order in a Java platform implementation. The happens-before relation mostly stresses orderings between two actions that conflict with each other, and defines when data races take place. There are a number of ways to induce a happens-before ordering, including:编程
- Each action in a thread happens-before every subsequent action in that thread.
- An unlock on a monitor happens-before every subsequent lock on that monitor.
- A write to a volatile field happens-before every subsequent read of that volatile.
- A call to start() on a thread happens-before any actions in the started thread.
- All actions in a thread happen-before any other thread successfully returns from a join() on that thread.
- If an action a happens-before an action b, and b happens before an action c, then a happensbefore c.
定义: 若是一个操做happens-before另外一个操做,那么意味着第一个操做的结果对第二个操做可见,并且第一个操做的执行顺序将排在第二个操做的前面。 两个操做之间存在happens-before关系,并不意味着Java平台的具体实现必须按照happens-before关系指定的顺序来执行。若是重排序以后的结果,与按照happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM容许这种重排序)。具体规则以下:安全
注:说明一下,网上搜出来有的是8条规则,我不知道还有两条哪儿来的,JSR-133 里面只有这六条。网上的还有下面两条:bash
程序顺序规则:一段代码在单线程中执行的结果是有序的。注意是执行结果,由于虚拟机、处理器会对指令进行重排序。虽然重排序了,可是并不会影响程序的执行结果,因此程序最终执行的结果与顺序执行的结果是一致的。故而这个规则只对单线程有效,在多线程环境下没法保证正确性。多线程
监视器锁规则:这个规则比较好理解,不管是在单线程环境仍是多线程环境,一个锁处于被锁定状态,那么必须先执行unlock操做后面才能进行lock操做。并发
volatile变量规则:这是一条比较重要的规则,它标志着volatile保证了线程可见性。通俗点讲就是若是一个线程先去写一个volatile变量,而后一个线程去读这个变量,那么这个写操做必定是happens-before读操做的。app
线程启动规则:假定线程A在执行过程当中,经过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。post
线程终结规则:假定线程A在执行的过程当中,经过制定ThreadB.join()等待线程B终止,那么线程B在终止以前对共享变量的修改在线程A等待返回后可见。学习
传递性规则:提现了happens-before原则具备传递性。
特别强调happens-hefore不能理解为“时间上的前后顺序”。 咱们来看以下代码:
public class VolatileTest {
private int a = 0;
private int getA() {
return a;
}
private void setA(int a) {
this.a = a;
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 100; i++) {
VolatileTest volatileTest = new VolatileTest();
Thread thread1 = new Thread(() -> {
volatileTest.setA(10);
});
thread1.start();
Thread thread2 = new Thread(() -> {
System.out.print(volatileTest.getA()+" ");
});
thread2.start();
}
}
}
复制代码
上面代码就是一组简单的setter/getter方法,如今假设如今有两个线程 thread1 和 thread2,线程 thread1 先(这里指时间上的先执行)执行setA(10),而后线程 thread2 访问同一个对象的getA()方法,那么此时线程B收到的返回值是对少呢?
答案:不肯定
0 0 0 0 10 0 10 10 10 0 10 0 10 10 10 10 10 0 10 10 0 0 0 10 0 10 10 10 0 10 0 10 10 10 0 10 10 0 10 10 10 0 0 10 10 0 10 0 10 10 10 10 10 10 10 10 10 10 0 0 0 10 10 0 10 0 10 0 0 0 10 10 0 10 10 10 10 10 10 10 10 10 10 10 0 10 10 10 0 10 10 10 10 10 0 10 0 10 0 0
复制代码
虽然线程 thread1 在时间上先于线程 thread2 执行,可是因为代码彻底不适用happens-before规则,所以咱们没法肯定先 thread2 收到的值时多少。也就是说上面代码是线程不安全的。
从图能够看出:
一个happens-before规则对应于一个或多个编译器和处理器重排序规则。对于Java程序员来讲,happens-before规则简单易懂,它避免Java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现方法.
时间前后顺序与happens-before原则之间基本没有太大的关系,因此咱们在衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以happens-before原则为准。
简单的说,happens-before 规则就是为了让程序猿更好的理解 JMM 提供的内存可见性而编写的规则,让程序猿能避免去学习编译器和底层编译原理的重排序规则。