关于“Java并发实践”的例子

我正在查看Brian Goetz的“Java Concurrency in Practice”中的代码示例。 他说,这个代码可能会停留在一个无限循环中,因为“准备就绪”的值可能永远不会被读者线程看到“。 我不明白这是怎么发生的…

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循环的开始处可能会caching该值while因为它在while循环内没有更改。 这是抖动优化代码的方法之一。

所以有可能线程在ready = true之前启动,并读取ready = false线程caching,并且永远不会再读取它。

检查volatile关键字 。

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

3.1.1陈旧的数据

NoVisibility展示了程序不完全同步的方式会导致令人惊讶的结果: 陈旧的数据 。 当读者线程ready ,可能会看到过时的值。 除非每次访问variables时都使用同步,否则可能会看到该variables的陈旧值。

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

在你提供的例子中,JVM可以推断ready字段可能不会在当前线程中被修改,所以它会用falsereplace!ready ,导致无限循环。 将该字段标记为volatile将导致JVM每次检查字段值(或者至less确保ready更改传播到正在运行的线程)。

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

诸如C的语言“inheritance”底层硬件的内存模型。 正在进行的工作是为C ++提供一个正式的内存模型,以便C ++程序可以在不同的平台上expression同样的东西。

 private static boolean ready; private static int number; 

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

Jeremy Manson和Brian Goetz :

在多处理器系统中,处理器通常具有一层或多层存储器高速缓冲存储器,通过加速对数据的访问(由于数据更接近处理器)并减less共享存储器总线上的stream量(由于可以满足许多存储器操作通过本地caching)。内存caching可以大大提高性能,但是它们提出了许多新的挑战。 例如,当两个处理器同时检查相同的内存位置时会发生什么? 在什么条件下他们会看到相同的价值?

所以,在你的例子中,这两个线程可能运行在不同的处理器上,每个处理器都有一个ready独立的caching。 Java语言提供了volatilesynchronized机制,以确保线程看到的值是同步的。

 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秒内启动,并优化检查并caching值或完全删除条件。 所以代码在可见性上会失败。

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