指令重新排序和发生在java之前的关系

Mar*_*tin 51 java concurrency multithreading java-memory-model

在Java Concurrency In Practice一书中,我们被告知可以通过编译器,JVM在运行时甚至由处理器重新排序程序的指令.因此,我们应该假设执行的程序不会以与我们在源代码中指定的顺序完全相同的顺序执行其指令.

但是,讨论Java内存模型的最后一章提供了一个先前发生的规则列表,指出了JVM保留了哪些指令排序.这些规则中的第一条是:

  • "程序顺序规则.线程中的每个操作都发生在程序顺序后面的该线程中的每个操作之前."

我相信"程序顺序"是指源代码.

我的问题:假设这个规则,我想知道什么指令可能实际重新排序.

"行动"定义如下:

Java内存模型是根据操作指定的,包括对变量的读取和写入,监视器的锁定和解锁,以及启动和连接线程.JMM定义了在程序中的所有操作之前调用的部分排序.为了保证执行动作B的线程可以看到动作A的结果(A和B是否出现在不同的线程中),必须在A和B之间的关系之前发生.在没有发生之前在两个之间进行排序操作,JVM可以随意重新排序.

其他提到的订单规则是:

  • 监控锁定规则.监视器锁定上的解锁发生在同一监视器锁定的每个后续锁定之前.
  • 易变变量规则.对每个后续读取同一字段之前发生对易失性字段的写入.
  • 线程启动规则.在线程上调用Thread.start会在启动线程中的每个操作之前发生.
  • 线程终止规则.线程中的任何操作都发生在任何其他线程检测到该线程已终止之前,可以通过成功从Thread.join返回,也可以通过Thread.isAlive返回false.
  • 中断规则.另一个线程上的线程调用中断发生在被中断的线程检测到中断之前(通过抛出InterruptedException,或者调用isInterrupted或中断).
  • 终结者规则.对象的构造函数的结束发生在该对象的终结器的开始之前.
  • 及物.如果A发生在B之前,B发生在C之前,那么A发生在C之前.

ass*_*ias 56

程序顺序规则的关键点是:在一个线程中.

想象一下这个简单的程序(所有变量最初为0):

T1:

x = 5;
y = 6;
Run Code Online (Sandbox Code Playgroud)

T2:

if (y == 6) System.out.println(x);
Run Code Online (Sandbox Code Playgroud)

从T1的角度来看,执行必​​须与在x(程序顺序)之后分配的y一致.然而,从T2的角度来看,情况并非如此,T2可能会打印0.

T1实际上允许首先分配y,因为2个分配是独立的,交换它们不会影响T1的执行.

通过适当的同步,T2将始终打印5或不打印.

编辑

你似乎误解了程序顺序的含义.程序订单规则归结为:

如果x并且y是同一个线程的动作并且在程序顺序x之前y,那么hb(x, y)(即x 发生在之前 y).

发生 - 之前在JMM中具有非常特定的含义.特别是,它意味着y=6必须之后x=5在T1从挂钟透视.它只表示T1执行的动作序列必须该顺序一致.您还可以参考JLS 17.4.5:

应该注意的是,两个动作之间存在的先发生关系并不一定意味着它们必须在实现中以该顺序发生.如果重新排序产生的结果与合法执行一致,则不是非法的.

在上面给出的示例中,您将同意从T1的角度(即在单线程程序中),x=5;y=6;y=6;x=5;您不读取值一致.在T1中,保证下一行的语句可以查看这两个操作,无论它们执行的顺序如何.

  • @Martin的一点是_happens-before_只是一个部分顺序 - 而`x = 5` _happens-before_`y = 6`由于程序顺序规则,这些指令之间根本没有_happens-before_关系和T2中发生的任何事情,所以T2可以看到`y = 6`的结果,但不是'x = 5'的结果.如果`y`是易变的那么那将强制执行T1中的'y = 6`和T2中的`if(y == 6)`之间的_happens-before_关系,在_if_ T2看到'y = 6` _then_它的意义上还必须看到`x = 5`. (11认同)