Java指令重新排序/线程缓存

时间:2013-01-14 15:18:42

标签: java multithreading caching

读一本书,这个代码出现了:

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;

    }

}

作者相对较快地提到了我感到惊讶的要点。

  1. 他们说ListenerThread可能永远不会终止。我想了几天(在我的脑后),我唯一的结论是它可能被ListenerThread缓存。真的吗?让ready volatile解决问题(因为它不应该再缓存它)吗?

  2. 他们还说程序可能会打印0.我现在明白Java可能会对指令进行重新排序,因此在更改数字之前就准备好了另一个线程。有没有任何方法(技术),除了将这些指令放在同步块中解决问题(在中央锁定值上)?我在想也许实现notify()/ wait(),但我觉得它会遭受同样的后果。避免这个问题的最佳方法是什么?

  3. 谢谢!

    编辑:

    我只是觉得我已经阅读了很多代码,并且很少有人为防止在多个线程中重新排序而烦恼。这有多常见?

2 个答案:

答案 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易变为足够。