指令重新排序和发生前关系

在《Java 并发实践》一书中,我们多次被告知,我们的程序的指令可以由编译器、运行时的 JVM 甚至处理器重新排序。因此,我们应该假设执行的程序不会以与我们在源代码中指定的顺序完全相同的顺序执行其指令。

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

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

我相信“程序顺序”指的是源代码。

我的问题是:假设有这个规则,我想知道什么指令实际上可以重新排序。

“操作”定义如下:

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

提到的其他顺序规则有:

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

答案 1

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

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

T1:

x = 5;
y = 6;

T2:

if (y == 6) System.out.println(x);

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

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

通过适当的同步,T2 将始终打印 5 个或什么都不打印。

编辑

你似乎误解了程序顺序的含义。程序顺序规则可归结为

如果 和 是同一线程的操作,并且按程序顺序排在前面,则(即 发生之前)。xyxyhb(x, y)xy

发生在之前在JMM中具有非常特定的含义。特别是,这并不意味着从挂钟的角度来看,必须在T1中之后。这仅意味着 T1 执行的操作序列必须与该顺序一致。您也可以参考 JLS 17.4.5y=6x=5

应该注意的是,两个操作之间存在“发生之前”关系并不一定意味着它们在实现中必须按该顺序发生。如果重新排序产生与合法执行一致的结果,则不违法。

在我上面给出的示例中,您将同意从T1的角度来看(即在单线程程序中),这是一致的,因为您不读取值。在 T1 中,保证下一行的语句能够看到这 2 个操作,而不管它们的执行顺序如何。x=5;y=6;y=6;x=5;


答案 2

推荐