在讨论线程之间动作可见性的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,删除无限循环?
实际行为是OS和JVM特定的。例如,默认情况下,Java在32位Windows上以客户端模式运行,在Mac上以服务器模式运行。在客户端模式下,work
方法将终止,但不会在服务器模式下终止。
这是因为Java服务器JIT编译器优化。 JIT编译器可以优化while循环,因为它没有看到变量done
在线程的上下文中发生变化。无限循环的另一个原因可能是因为一个线程可能最终从其寄存器或缓存中读取标志的值而不是进入内存。因此,它可能永远不会看到另一个线程对此标志所做的更改。
基本上通过添加volatile
你使线程拥有done
标志不缓存此标志。因此,boolean
值存储在公共存储器中,因此保证可见性。此外,通过使用volatile
,您可以禁用可以内联标志值的JIT优化。
基本上如果你想重现无限循环 - 只需在服务器模式下运行你的程序:
java -server VolatileTest