从JDK5开始,JMM就使用happens-before的概念来阐述多线程之间的内存可见性,在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在同一个线程内,也可以是在不同的线程之间。
happens-before原则
happens-before原则非常重要,它是判断数据是否存在竞争、线程是否安全的主要依据,依靠这个原则,我们解决在并发环境下两操作之间是否可能存在冲突的所有问题。
举例就是
i=1; // 线程A
j=i; // 线程B
j 是否等于1呢?假定线程A的操作(i = 1)happens-before线程B的操作(j = i),那么可以确定线程B执行后j = 1 一定成立,如果他们不存在happens-before原则,那么j = 1 不一定成立。
happens-before原则定义如下:
-
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
-
两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。
第一条是对程序员而言,方便程序员使用,可以这样理解happens-before关系:如果A happens-before B,那么Java内存模型将向程序员保证——A操作的结果将对B可见,且A的执行顺序排在B之前。注意,这只是Java内存模型向程序员做出的保证!实际并不是真正的不可重排序。
第二条是编译器和处理器重排序而言的约束原则。只要不改变程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处理器怎么优化都行。所以说,第一条只是方便程序员使用时方便,程序员关系的只是结果,只要结果不变,是否真的被重排序并不重要。
happens-before规则
-
程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。(这里我觉得可以通俗的总结下,就是程序是从上而下执行的。)
-
监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
-
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。(当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存上。当读一个volatile变量时,JMM会把该线程对应的本地缓存置为无效,从主内存中重新读取变量。)
-
传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
-
start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
-
join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
-
程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
-
对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始。
happens-before与JMM的关系
如图所示,一个happens-before规则定义一个或多个编译器和处理器重排序规则。它避免java程序员为了理解JMM提供的内存可见性保证而去学习复杂的重排序规则以及这些规则的具体实现方法。
评论区