JVM/JIT的栅栏指令插入

3
Java内存模型提供DRF保证(数据竞争自由),这意味着在Java的松散内存模型下执行无数据竞争的程序将会得到与顺序一致执行相同的结果。我有以下问题: a)对于存在数据竞争的程序,编译器(具体来说是任何JVM实现)是否进行延迟集分析/线程逃逸分析等操作以查找需要插入的栅栏指令,使其无数据竞争?还是基于它正在执行的位置,JIT会做这个操作?
b)如果编译器(在这种情况下是JVM)这样做,为什么我们不能只写有数据竞争的程序,因为编译器无论如何都会将其转换为无数据竞争的程序?如果编译器无论如何都会将其转换为无数据竞争的程序,那么如何有意地编写有数据竞争的程序,例如Java中的某些并发数据结构的实现?
c)或者第三种可能性是JVM本身不会将有数据竞争的程序转换为无数据竞争的程序,但存在其他分析方法可以为我们完成此操作。这是真的吗?
1个回答

3
给定一个有竞争的程序,编译器(具体来说是任何JVM实现)是否会进行延迟设置分析/线程逃逸分析等来查找需要插入的栅栏指令,以使其无竞争?或者是基于它在哪里执行而由JIT来执行?
内存栅栏指令是特定架构指令集中的指令。 JVM指令集中没有等价的指令。因此,实际上是JVM/JIT向处理器发出栅栏指令。
如果编译器(在这种情况下是JVM)确实这样做,那么为什么我们不能只写有竞争的程序,因为编译器无论如何都会将其转换为无竞争的程序?如果编译器无论如何都会这样做(通过插入栅栏使其无竞争),那么如何编写有竞争的程序(有意地编写),例如Java中某些并发数据结构的实现?
编译器只会确保在生成字节码时,JVM中对变量执行的所有操作都遵守Java内存模型中指定的规则。具体来说,在优化方面,只要不影响必须存在于操作之间或代码中受保护区域的同步顺序之间的happens-before关系,编译器就可以优化任何一组指令。例如,编译器不会重新组织对易失性变量的读取和写入。它还将确保在进入或离开受保护(同步)代码区域时不违反happens-before关系。
因此,将编译器转换“有竞争”的程序为无竞争的程序的说法是不正确的。实际上,在优化后,被认为是无竞争的程序(但不符合Java内存模型)可能会变成“有竞争”的程序。
Java中的并发数据结构的实现依赖于Java内存模型提供的保证。具体来说,这是从Java 5开始修订的Java内存模型,其中准确指定了易失性变量的读取和写入之间的happens-before关系。 java.util.concurrent包中的ConcurrentXXX类严重依赖于易失性读取的承诺行为,以确保无竞争行为。根据Java内存模型,对易失性变量的写入如果是程序顺序,则保证在读取之前进行;简单地说,易失性读取将始终检索变量中最准确的数据版本。并发类利用这一点,以确保数据结构可以由单个线程更新,同时由多个其他线程读取(在任何其他情况下,都会存在竞争条件)。
或者第三种可能性是JVM本身不会将“有竞争”的程序转换为“无竞争”的程序,但存在其他分析可以为我们完成。是这种情况吗?
JVM发出内存栅栏指令。它不会将“有竞争”的程序转换为“无竞争”的程序。如果编译器生成的字节码遵循Java内存模型,则JVM/JIT将在必要时发出内存栅栏指令-在读取/写入易失性变量、获取或释放对象上的监视器等时。
冒着重复的风险,无论是JVM还是编译器都不会将“有竞争条件”的程序转化为无竞争条件的程序,反之亦然。任何相反的行为都是Java内存模型或JVM中错误的bug。您需要编写一个无竞争条件的程序,通过理解程序顺序、同步顺序和发生在之前的顺序,编译器和JVM将保证在运行时确保它。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接