关于“Java Concurrency in Practice”示例的问题

时间:2009-12-17 04:29:06

标签: java concurrency

我正在查看Brian Goetz撰写的“Java Concurrency in Practice”中的代码示例。他说这段代码可能会保持无限循环,因为“'ready'的值可能永远不会对读者线程可见”。我不明白这是怎么发生的......

public class NoVisibility {
    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }

    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    } 
}

6 个答案:

答案 0 :(得分:28)

因为ready未标记为volatile,并且该值可能会在while循环的开头缓存,因为它在while循环内没有更改。这是抖动优化代码的方式之一。

因此,线程可能在ready = true之前启动,并且读取本地线程的ready = false缓存,而不再读取它。

查看the volatile keyword

答案 1 :(得分:8)

原因在代码示例之后的部分解释。

3.1.1陈旧数据

  

NoVisibility展示了不充分同步的程序可能导致令人惊讶的结果的方式:陈旧数据。当读者线程检查ready时,它可能会看到过时的值。除非在每次访问变量时使用,否则可以看到该变量的陈旧值。

答案 2 :(得分:6)

Java内存模型允许JVM优化引用访问,例如,如果它是单线程应用程序,除非该字段标记为volatile或具有锁定的访问(故事有点)实际上很复杂的锁。)

在您提供的示例中,JVM可能会推断当前线程中可能未修改ready字段,因此它会将!ready替换为false,从而导致无限循环。将字段标记为volatile将导致JVM每次都检查字段值(或者至少确保ready更改传播到正在运行的线程)。

答案 3 :(得分:4)

问题源于硬件 - 每个CPU在缓存一致性,内存可见性和操作重新排序方面都有不同的行为。 Java在这里比C ++更好,因为它定义了一个所有程序员都可以信赖的跨平台内存模型。当Java在内存模型比Java内存模型所需的内存模型弱的系统上运行时,JVM必须弥补差异。

C语言“继承”底层硬件的内存模型。正在努力为C ++提供正式的内存模型,以便C ++程序在不同的平台上具有相同的意义。

答案 4 :(得分:2)

private static boolean ready;
private static int number;

内存模型的工作方式是每个线程都可以读取和写入这些变量的自身副本(问题也会影响非静态成员变量)。这是底层架构可以工作的结果。

Jeremy Manson and Brian Goetz

  

在多处理器系统中,处理器通常具有一层或多层内存缓存,通过加快对数据的访问(因为数据更靠近处理器)和减少共享内存总线上的流量(因为许多内存操作)来提高性能可以通过本地缓存来满足。)内存缓存可以极大地提高性能,但它们带来了许多新的挑战。例如,当两个处理器同时检查相同的内存位置时会发生什么?在什么条件下他们会看到相同的价值?

因此,在您的示例中,两个线程可能在不同的处理器上运行,每个处理器在其自己的单独缓存中都有ready的副本。 Java语言提供volatilesynchronized机制,以确保线程看到的值保持同步。

答案 5 :(得分:2)

public class NoVisibility {

    private static boolean ready = false;
    private static int number;

    private static class ReaderThread extends Thread {

        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(number);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ReaderThread().start();
        number = 42;
        Thread.sleep(20000);
        ready = true;
    }
}

将Thread.sleep()调用放置20秒,JIT将在这20秒内启动,它将优化检查并缓存值或完全删除条件。因此代码将失去可见性。

要阻止这种情况发生,你必须使用volatile