关于“Java并发实践”的问题示例

问题描述:

我正在查看Brian Goetz的“Java Concurrency in Practice”中的代码示例。他说,这段代码可能会停留在一个无限循环中,因为“准备好”的值可能永远不会被读者线程看到“。我不明白会发生些什么......关于“Java并发实践”的问题示例

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; 
    } 
} 

因为ready没有被标记为volatile和值可在while循环的开始被缓存,因为它不是while环路内改变。这是抖动优化代码的方式之一。

因此,这可能是线程ready = true之前启动并读取ready = false缓存是线程本地永不再读取它。

结帐the volatile keyword

+0

如果“就绪”是易失性的,但数字不是,是否可以将数字打印为0? – zhiyuany 2016-07-21 22:35:56

+0

嗨Qberticus,这只是一个澄清,如果主线程在读者线程之前完成,那么我们有机会打印第42号对吗? – 2017-03-09 13:54:08

原因在代码示例之后的部分中进行了解释。

3.1.1陈旧数据

NoVisibility证明重要的是充分地同步的程序可能会导致意想不到的结果的方法:陈旧数据。当读者线程检查ready时,它可能会看到过时的值。每次访问变量时,除非使用同步,否则可能会看到该变量的陈旧值。

Java存储模型允许优化参考JVM访问和诸如如果它是一个单线程应用程序,除非该字段被标记为volatile或正与一个锁的访问(故事变得有点复杂实际上有锁)。

在这个例子中,你所提供的JVM可以推断,ready场可能不是当前线程中进行修改,所以它会与false取代!ready,引起无限循环。将该字段标记为volatile将导致JVM每次都检查字段值(或至少确保ready更改传播到正在运行的线程)。

+0

只是有点好奇,为什么JVM会不小心用'false'替换'!ready'? ''ready''不是'ReaderThread'中的字段。 – sleepsort 2013-06-27 14:51:26

+0

因为除非它被标记为易失性,否则JVM没有义务根据Java内存模型重新读取它。这种优化(变量提升)实际上很常见。 – assylias 2015-11-30 09:10:35

private static boolean ready; 
private static int number; 

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

Jeremy Manson and Brian Goetz

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

所以,在你的例子,两个线程可能在不同的处理器,在自己的,独立缓存的一个副本ready运行,每个。 Java语言提供了volatile和​​机制以确保线程看到的值同步。

+0

谢谢大家 - 现在有道理。所以这是JVM将Java代码映射为可执行代码的具体方式。我认为在C++中会出现类似的问题?有谁知道C++如何处理这个问题? – 2009-12-17 14:48:54

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

像C的语言“继承”底层硬件的内存模型。正在进行的工作是为C++提供正式的内存模型,以便C++程序在不同的平台上可以表达同样的意思。

+0

我不知道这是不是真正的布莱恩戈茨,但为了真正把这一点推到家里,我很想看到一个实际上失败的例子。我已经试过了示例代码,即使执行很多次,也无法让它失败。有什么建议? – jbu 2017-06-03 09:30:10

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

+0

在数十次试验后,这似乎不会失败。使用java 1.8 – jbu 2017-06-03 09:24:57