Java并发(2)- 聊聊happens-before

上一篇文章聊到了Java内存模型,在其中我们说JMM是建立在happens-before(先行发生)原则之上的。
为什么这么说呢?因为在Java程序的执行过程中,编译器和处理器对我们所写的代码进行了一系列的优化来提高程序的执行效率。这其中就包括对指令的“重排序”。
重排序导致了我们代码并不会按照代码编写顺序来执行,那为什么我们在程序执行后结果没有发生错乱,原因就是Java内存模型遵循happens-before原则。在happens-before规则下,不管程序怎么重排序,执行结果不会发生变化,所以我们不会看到程序结果错乱。

重排序

重排序是什么?通俗点说就是编译器和处理器为了优化程序执行性能对指令的执行顺序做了一定修改。
重排序会发生在程序执行的各个阶段,包括编译器冲排序、指令级并行冲排序和内存系统重排序。这里不具体分析每个重排序的过程,只要知道重排序导致我们的代码并不会按照我们编写的顺序来执行。
在单线程的的执行过程中发生重排序后我们是无法感知的,如下代码所示,

int a = 1; //步骤1 int b = 2; //步骤2 int c = a + b; //步骤3

1和2做了重排序并不会影响程序的执行结果,在某些情况下为了优化性能可能会对1和2做重排序。2和3的重排序会影响执行结果,所以编译器和处理器不会对2和3进行重排序。
在多线程中如果没有进行正确的同步,发生重排序我们是可以感知的,比如下面的代码:

public class AAndB { int x = 0; int y = 0; int a = 0; int b = 0; public void awrite() { a = 1; x = b; } public void bwrite() { b = 1; y = a; } } public class AThread extends Thread{ private AAndB aAndB; public AThread(AAndB aAndB) { this.aAndB = aAndB; } @Override public void run() { super.run(); this.aAndB.awrite(); } } public class BThread extends Thread{ private AAndB aAndB; public BThread(AAndB aAndB) { this.aAndB = aAndB; } @Override public void run() { super.run(); this.aAndB.bwrite(); } } private static void testReSort() throws InterruptedException { AAndB aAndB = new AAndB(); for (int i = 0; i < 10000; i++) { AThread aThread = new AThread(aAndB); BThread bThread = new BThread(aAndB); aThread.start(); bThread.start(); aThread.join(); bThread.join(); if (aAndB.x == 0 && aAndB.y == 0) { System.out.println("resort"); } aAndB.x = aAndB.y = aAndB.a = aAndB.b = 0; } System.out.println("end"); }

如果不进行重排序,程序的执行顺序有四种可能:

Java并发(2)- 聊聊happens-before


Java并发(2)- 聊聊happens-before


Java并发(2)- 聊聊happens-before


Java并发(2)- 聊聊happens-before


但程序在执行多次后会打印出“resort”,这种情况就说明了A线程和B线程都出现了重排序。

Java并发(2)- 聊聊happens-before

happens-before的定义

happens-before定义了八条规则,这八条规则都是用来保证如果A happens-before B,那么A的执行结果对B可见且A的执行顺序排在B之前。

程序次序规则:在一个单独的线程中,按照程序代码的执行流顺序,(时间上)先执行的操作happen—before(时间上)后执行的操作。

管理锁定规则:一个unlock操作happen—before后面(时间上的先后顺序,下同)对同一个锁的lock操作。

volatile变量规则:对一个volatile变量的写操作happen—before后面对该变量的读操作。

线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。

线程终止规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。

线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件的发生。

对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。

传递性:如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before操作C。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpszzg.html