在JSR-133 section 3.1中,它讨论了线程之间动作的可见性-提到下面的代码示例,如果两个线程正在运行,则该示例不使用boolean字段的volatile关键字,可以变成无限循环。它。这是来自JSR的代码:
class LoopMayNeverEnd {
boolean done = false;
void work() {
while (!done) {
// do work
}
}
void stopWork() {
done = true;
}
}
以下是我感兴趣的那部分重要内容的引文:
...现在想象一下,创建了两个线程,其中一个 线程调用work(),并且在某些时候,另一个线程调用stopWork()。因为有 在两个线程之间没有任何关系发生关系之前,循环中的线程可能永远不会 查看其他线程执行的更新...
这是我自己编写的Java代码,以便可以看到它的循环:
public class VolatileTest {
private boolean done = false;
public static void main(String[] args) {
VolatileTest volatileTest = new VolatileTest();
volatileTest.runTest();
}
private void runTest() {
Thread t1 = new Thread(() -> work());
Thread t2 = new Thread(() -> stopWork());
t1.start();
t2.start();
}
private void stopWork() {
done = true;
System.out.println("stopped work");
}
private void work() {
while(!done){
System.out.println("started work");
}
}
}
尽管连续执行的结果有所不同-正如预期的那样-我看不到它会陷入无限循环。我试图了解如何模拟文档中建议的无限循环,我缺少了什么?声明布尔值 volatile 如何删除无限循环?
答案 0 :(得分:1)
实际行为是特定于OS和JVM的。例如,默认情况下,Java在32位Windows上以客户端模式运行,在Mac上以服务器模式运行。在客户端模式中,1 q 11
2 w 22
3 e 33
方法将终止,但不会在服务器模式中终止。
发生这种情况是由于Java服务器JIT编译器优化。 JIT编译器可以优化while循环,因为它看不到变量work
在线程上下文内发生变化。无限循环的另一个原因可能是因为一个线程最终可能会从其寄存器或缓存中读取标志的值,而不是转到内存中。结果,它可能永远不会看到另一个线程对该标志所做的更改。
基本上,通过添加done
,可以使拥有volatile
标志的线程不缓存该标志。因此,done
值存储在公用内存中,因此可以保证可见性。另外,通过使用boolean
,您可以禁用可以内联标志值的JIT优化。
基本上,如果要重现无限循环-只需在服务器模式下运行程序即可:
volatile
答案 1 :(得分:1)
所有 Java 值的默认、非易失性、隐式声明允许 Jit 编译器在循环外“提升”对非易失性值的引用,以便它们只能“一次”读取。在执行路径的跟踪可以安全地得出这样一个事实之后,允许这样做,即在这样的循环内调用的方法,不要每次都导致重新进入类方法,在那里它可能会改变这些非易失性值的值。< /p>
最终的问题是这种引用提升取决于值的突变的“可达性”。因此,在开发过程中,您可能会遇到无法达到值的突变的时刻,因此提升发生了,并且您突然无法退出循环。稍后对循环的更改可能会使用某些函数,这使得无法辨别值无法被循环中的逻辑写入,并且提升器消失并且循环再次工作。
Java 并发邮件列表最近/当前有关于此问题的讨论。他们似乎不相信这对 Java 开发人员来说是个问题,而且这种引用的“优化”对性能的价值远大于对开发的问题。