3

读一本书,出现以下代码:

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 缓存了。真的吗?制作readyvolatile 会解决问题吗(因为它不应该再缓存它了)?

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

谢谢!

编辑:

我只是觉得我已经阅读了很多代码,很少有人会费心防止在多个线程中重新排序。这有多普遍?

4

2 回答 2

8

我唯一的结论是它可能被那个 ListenerThread 缓存了。真的吗?准备好易失性会解决问题吗(因为它不应该再缓存它了)?

不仅缓存,而且 JIT 可以在线程从不更改值的基础上内联该值。即它变得硬编码。

使用 volatile 可防止做出此类假设。它将强制它每次读取缓存一致的副本。

我现在明白 Java 可能会重新排序指令,

不仅是 Java,CPU 也可以重新排序指令。JIT 知道 CPU 可以进行这种重新排序,而 AFAIK 它很少需要这样做,因为它假设 CPU 会做得很好。

顺便说一句,访问 volatile 变量还可以防止指令重新排序,因此使readyvolatile 可以解决这两个问题。

于 2013-01-14T15:35:24.817 回答
3

他们说 ListenerThread 可能永远不会终止。我想了几天,唯一的结论是它可能被那个ListenerThread缓存了。真的吗?准备好易失性会解决问题吗(因为它不应该再缓存它了)?

这是真的,是的。是的,声明变量volatile会改变变量的内存访问语义,并在每次访问变量时强制重新读取。

他们还说程序可能会打印 0。我现在明白 Java 可能会重新排序指令,因此在更改数字之前准备就绪对另一个线程是正确的。除了将这两条指令放在同步块中之外,还有什么方法可以解决问题?我在想也许可以实现 notify()/wait(),但我觉得它会遭受同样的后果。

这是因为没有顺序保证,所以 JVM 可以自由地对变量赋值进行重新排序。如果你想让number双方都能看到,你必须防止这种重新排序,正如@PeterLawrey 所说,让readyvolatile 足以做到这一点。

于 2013-01-14T15:26:26.167 回答