通过 JVM/JIT 进行栅栏指令插入
Java内存模型提供了DRF保证(数据竞争自由),这意味着在Java的宽松内存模型下执行时,无数据竞争的程序将给出与顺序一致执行相同的行为。我有以下问题: a) 给定一个 racy 程序,编译器(特别是任何 jvm 实现)是否进行延迟集分析/线程逃逸分析等来找出需要插入的栅栏指令以使其无竞争?或者 JIT 是否根据执行位置来执行此操作?
b) 如果编译器做到了(在本例中为 jvm),为什么我们不能只编写 racy 程序,因为编译器无论如何都会将其转换为无竞争程序?如果编译器有任何办法可以做到这一点(通过栅栏插入使其无竞争),那么如何才能(故意)编写活泼的程序,例如java中并发数据结构的一些实现?
c) 或者第三种可能性,jvm 本身不会将 racy 转换为无竞争程序,但存在其他分析可以为我们做到这一点。是这样吗?
Java memory model gives DRF guarantee(Data race freedom) which means that a data race free program when executed under relaxed memory model of java will give same behaviour as of sequentially consistent execution. I have the following question:
a) Given a racy program, does compiler (to be very specific any jvm implementation )do the delay set analysis/thread escape analysis etc to find out the fence instructions that need to be inserted to make it race-free? or doest JIT do it based on where is it getting executed?
b) If compiler does it (jvm in this case) why cant we just write racy program because compiler is anyway going to convert it into race-free program? And if compiler is any way going to do it (make it race-free by fence insertion), how can one write racy programs(intentionally), like some implementations of concurrent data structures in java?
c) Or third possibility that jvm itself does not convert racy to race-free program but there exists other analysis that can do it for us. Is it the case?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
内存栅栏指令特定于架构的指令集。它们不是 JVM 指令集中的等效指令。因此,实际上是 JVM/JIT 向处理器发出栅栏指令。
编译器只会确保在生成字节码时,对 JVM 中的变量执行的所有操作都遵守 Java 内存模型中指定的规则。具体来说,在优化领域,编译器可以自由地优化任何指令集,只要它不影响动作之间必须存在的事前发生关系或动作之间的同步顺序。例如,编译器不会重新组织对易失性变量的读取和写入。它还将确保在进入或离开受保护(同步)的代码区域时不会违反先发生关系。
因此,编译器将“有氧”程序转换为无竞争程序的说法是不正确的。事实上,一个假定为无竞争的程序(但不是在 Java 内存模型下)在优化后可能会变成“有竞争的”程序。
Java 中数据结构的并发实现依赖于 Java 内存模型提供的保证。具体来说,这是从 Java 5 开始修订的 Java 内存模型,其中准确指定了 易失性变量的读取和写入之间的发生之前关系。
java.util.concurrent
包中的 ConcurrentXXX 类在很大程度上依赖于这种承诺的易失性读取行为来确保无竞争行为。在Java内存模型下,如果程序顺序如此,则保证对易失性变量的写入发生在读取之前;简而言之,易失性读取将始终检索变量中数据的最准确版本。并发类利用这一点来确保数据结构可以由单个线程更新,同时由多个其他线程读取(在任何其他情况下,都会存在竞争条件)。JVM 发出内存栅栏指令。它不会将“活泼”程序转换为“无竞争”程序。如果编译器生成遵循 Java 内存模型的字节码,那么 JVM/JIT 将在必要时发出内存栅栏指令 - 当读/写易失性变量、获取或释放对象上的监视器等时。
冒着重复自己的风险,无论是JVM 和编译器都不会将“活泼”程序转换为无竞争程序,反之亦然。任何相反的行为都是 Java 内存模型或 JVM 中的错误。您需要将程序编写为无竞争的程序,通过了解程序顺序、同步顺序和发生前顺序,编译器和 JVM 将保证它在运行时得到保证。
我建议您阅读 InfoQ 上的这篇文章,了解有关 JVM 如何发出内存栅栏的更多详细信息指令并保证 Java 内存模型所做的承诺。
Memory fence instructions are specific to the instruction set of an architecture. They are no equivalent instructions in the JVM's instruction set. Therefore, it is the JVM/JIT that actually issues the fence instructions to the processor.
The compiler will only ensure that when it generates byte code, all actions performed on variables in a JVM obey the rules specified in the Java memory model. Specifically, in the area of optimization, the compiler is free to optimize any set of instructions as long as it does not affect the happens-before relationships that must exists between actions, or the synchronization order amongst actions. For instance, the compiler will not reorganize reads and writes on volatile variables. It will also ensure that happens-before relationships are not violated when entering or leaving guarded (synchronized) regions of code.
So the statement that the compiler will convert a "racy" program into a race-free one is incorrect. In fact, a program assumed to race-free (but not under the Java memory model) may become a "racy" one after optimizations.
The concurrent implementations of data-structures in Java, rely on the guarantees provided by the Java memory model. Specifically, this is the revised Java memory model from Java 5, where the happen-before relationships between reads and writes of volatile variables, was specified accurately. The ConcurrentXXX classes in the
java.util.concurrent
package, relies heavily on this promised behavior of volatile reads to ensure race-free behavior. Under the Java memory model, a write to a volatile variable is guaranteed to happen before a read, if that is the program order; in simpler words, the volatile read will always retrieve the most accurate version of data in the variable. The concurrent classes utilize this to ensure that the data structures can be updated by a single thread, while being read by multiple other threads (in any other scenario, there would be race conditions).The JVM issues the memory fence instructions. It does not perform any conversion of "racy" programs to "race-free" ones. If the compiler generated bytecode that obeys the Java memory model, then the JVM/JIT will issue memory fence instructions when necessary - when reading/writing volatile variables, acquiring or releasing the monitors on objects etc.
At the risk of repeating myself, neither the JVM nor the compiler will convert a "racy" program into a race-free one, or vice versa. Any behavior to the contrary is a bug in either the Java memory model, or in the JVM. You will need to write the program as a race-free one, by understanding the program order, the synchronization order and the happens-before order, and the compiler and the JVM will guarantee that it will be ensured at runtime.
I would encourage you to read this article at InfoQ, for further details on how the JVM issues memory fence instructions and guarantees the promises made by the Java memory model.