读一本书,这个代码出现了:
public class Test {
private static boolean ready = false;
private static int number = 0;
public static class ListenerThread extends Thread {
public void run() {
while(!ready) {
Thread.yield();
}
System.out.println(number);
}
}
public static void main (String[] args) {
new ListenerThread().start();
number = 10;
ready = true;
}
}
作者相对较快地提到了我感到惊讶的要点。
他们说ListenerThread可能永远不会终止。我想了几天(在我的脑后),我唯一的结论是它可能被ListenerThread缓存。真的吗?让ready
volatile解决问题(因为它不应该再缓存它)吗?
他们还说程序可能会打印0.我现在明白Java可能会对指令进行重新排序,因此在更改数字之前就准备好了另一个线程。有没有任何方法(技术),除了将这些指令放在同步块中解决问题(在中央锁定值上)?我在想也许实现notify()/ wait(),但我觉得它会遭受同样的后果。避免这个问题的最佳方法是什么?
谢谢!
编辑:
我只是觉得我已经阅读了很多代码,并且很少有人为防止在多个线程中重新排序而烦恼。这有多常见?
答案 0 :(得分:8)
我唯一的结论是它可能被ListenerThread缓存。真的吗?准备挥发性会解决问题(因为它不应该再缓存它了吗?)
不仅缓存,而且JIT可以在线程永远不会更改它的基础上内联值。即它变硬了。
使用volatile可以防止这样的假设。它会强制它每次都读取缓存一致的副本。
我现在明白Java可能会重新排序指令,
不仅是Java,而且CPU可以重新排序指令。 JIT意识到CPU可以执行此重新排序,并且它很少需要AFAIK,因为它假设CPU将做得很好。
BTW访问volatile变量也会阻止指令重新排序,因此ready
volatile会解决这两个问题。
答案 1 :(得分:3)
他们说ListenerThread可能永远不会终止。我想了几天,我唯一的结论就是它可能被ListenerThread缓存了。真的吗?准备挥发性会解决问题(因为它不应该再缓存它了吗?)
这是真的,是的。是的,声明变量volatile
会更改变量的内存访问语义,并在每次访问变量时强制重新读取。
他们还说该程序可能会打印0.我现在明白Java可能会重新排序指令,因此在更改数字之前就准备好了另一个线程。有没有任何方法,除了将这两个指令放在同步块中解决问题?我想也许实现notify()/ wait(),但我觉得它会遭受同样的后果。
这是因为无法保证排序,因此JVM可以自由重新排序变量赋值。如果你想让number
对双方都同样可见,你必须阻止这种重新排序,正如@PeterLawrey所说,使ready
易变为足够。