关于“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
字段可能不会在当前线程中被修改,所以它会用false
replace!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语言提供了volatile
和synchronized
机制,以确保线程看到的值是同步的。
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
。