happens-before的概念阐述操作之间的内存可见性,是用来避免胡乱的代码指令重排序而导致的代码出现一些问题.
happens-before规则制定了在一些特殊的情况下,不允许编辑器和指令器对你写的代码进行指令重排,必须保证你的代码有序性.
JMM这么做的原因是:程序员对于这两个操作是否真的被重排序并不关心,程序员关心的是程序执行时的语义不能被改变(即执行结果不能被改变)。因此,happens-before关系本质上和as-if-serial语义是一回事。as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多线程程序的执行结果不被改变。
A happens-before B,在Java内存模型中,happens-before 应该翻译成:前一个操作的结果可以被后续的操作获取。讲白点就是前面一个操作把变量a赋值为1,那后面一个操作b肯定能知道a已经变成了1。
现在电脑都是多CPU,并且都有缓存,导致多线程直接的可见性问题.所以为了解决多线程的可见性问题,就搞出了happens-before原则,让线程之间遵守这些原则。编译器还会优化我们的语句,所以等于是给了编译器优化的约束。不能让它优化的不知道东南西北了!
两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前(the first is visible to and ordered before the second).在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系 。
加深理解
上面的定义看起来很矛盾,其实它是站在不同的角度来说的。
1)站在Java程序员的角度来说:JMM保证,如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2)站在编译器和处理器的角度来说:JMM允许,两个操作之间存在happens-before关系,不要求Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序是允许的。
回顾我们前面存在数据依赖性的代码:
站在我们Java程序员的角度:
但是仔细考察,2、3是必需的,而1并不是必需的,因此JMM对这三个happens-before关系的处理就分为两类:
1.会改变程序执行结果的重排序
2.不会改变程序执行结果的重排序
JMM对这两种不同性质的重排序,采用了不同的策略,如下:
1.对于会改变程序执行结果的重排序,JMM要求编译器和处理器必须禁止这种重排序;
2.对于不会改变程序执行结果的重排序,JMM对编译器和处理器不做要求。
于是,站在我们程序员的角度,看起来这个三个操作满足了happens-before关系,而站在编译器和处理器的角度,进行了重排序,而排序后的执行结果,也是满足happens-before关系的。