3

Java 内存模型提供了 DRF 保证(数据竞争自由),这意味着当在 Java 的宽松内存模型下执行时,无数据竞争的程序将给出与顺序一致执行相同的行为。我有以下问题:a)给定一个活泼的程序,编译器(非常具体的任何jvm实现)是否进行延迟集分析/线程逃逸分析等以找出需要插入的栅栏指令以使其竞赛-自由的?还是 JIT 是根据它在哪里执行的?

b)如果编译器做到了(在这种情况下为jvm),为什么我们不能只编写活泼的程序,因为编译器无论如何都会将其转换为无竞争的程序?如果编译器有任何方法可以做到这一点(通过插入栅栏使其无竞争),那么如何(有意地)编写活泼的程序,比如java中并发数据结构的一些实现?

c) 或者 jvm 本身不会将 racy 转换为无种族程序的第三种可能性,但存在其他分析可以为我们做到这一点。是这样吗?

4

1 回答 1

3

给定一个活泼的程序,编译器(特别是任何 jvm 实现)是否会进行延迟集分析/线程逃逸分析等以找出需要插入的栅栏指令以使其无竞争?还是 JIT 是根据它在哪里执行的?

内存栅栏指令特定于架构的指令集。它们不是 JVM 指令集中的等效指令。因此,实际上向处理器发出栅栏指令的是 JVM/JIT。

如果编译器做到了(在这种情况下为 jvm),为什么我们不能只编写 racy 程序,因为编译器无论如何都会将其转换为无竞争程序?如果编译器有任何方法可以做到这一点(通过插入栅栏使其无竞争),那么如何(有意地)编写活泼的程序,比如java中并发数据结构的一些实现?

编译器只会确保在生成字节码时,对 JVM 中的变量执行的所有操作都遵守 Java 内存模型中指定的规则。具体来说,在优化领域,编译器可以自由地优化任何指令集,只要它不影响动作之间必须存在的happens-before关系或动作之间的同步顺序。例如,编译器不会重新组织对 volatile 变量的读取和写入。它还将确保在进入或离开代码的受保护(同步)区域时不会违反发生之前的关系。

因此,编译器将“活泼”程序转换为无竞争程序的说法是不正确的。事实上,一个假定为无竞争的程序(但不是在 Java 内存模型下)在优化后可能会变成一个“活泼”的程序。

Java 中数据结构的并发实现依赖于 Java 内存模型提供的保证。具体来说,这是从 Java 5 修改后的 Java 内存模型,其中准确指定了 volatile 变量的读取和写入之间的发生前关系。中的 ConcurrentXXX 类java.util.concurrent包,很大程度上依赖于这种承诺的易失性读取行为来确保无竞争行为。在 Java 内存模型下,对 volatile 变量的写入保证在读取之前发生,如果这是程序顺序的话;简而言之,易失性读取将始终检索变量中最准确的数据版本。并发类利用这一点来确保数据结构可以由单个线程更新,同时由多个其他线程读取(在任何其他情况下,都会有竞争条件)。

或者 jvm 本身不会将 racy 转换为无竞争程序的第三种可能性,但存在其他分析可以为我们做到这一点。是这样吗?

JVM 发出内存栅栏指令。它不会执行将“racy”程序转换为“race-free”程序的任何转换。如果编译器生成的字节码遵循 Java 内存模型,那么 JVM/JIT 将在必要时发出内存栅栏指令——读取/写入 volatile 变量、获取或释放对象上的监视器等。

冒着重蹈覆辙的风险,JVM 和编译器都不会将“活泼”的程序转换为无竞争的程序,反之亦然。任何相反的行为都是 Java 内存模型或 JVM 中的错误。您需要将程序编写为无竞争的程序,通过了解程序顺序、同步顺序和发生前的顺序,编译器和 JVM 将保证它在运行时得到保证。

我建议您阅读InfoQ 上的这篇文章,了解有关 JVM 如何发出内存栅栏指令并保证 Java 内存模型做出的承诺的更多详细信息。

于 2011-07-12T18:08:38.553 回答