出于稀薄的空气安全

Java Concurrency in Practice解释了这个概念:

当一个线程在没有同步的情况下读取一个变量时,它可能会看到一个陈旧的值,但至少它会看到某个线程实际放置的值而不是某个随机值。 这种安全保证称为超薄空气安全。

这种安全性是否很弱,因为它可能包含陈旧价值?

也许这个片段, at least it sees a value that was actually placed there by some thread than some random value ,因为本书上一个主题是JVM在sharing variables without synchronization重新命令变量语句参考sharing variables without synchronization的可能性?

示例:根据重新排序:42或0可以打印出来。

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

编辑 – 删除“请评论”评论。

这种安全性是否很弱,因为它可能包含陈旧价值?

是。 “Java Concurrency in Practice”中的引用试图指出您的number可能是042具体取决于访问非同步字段所固有的竞争条件,但它不会(假设) 1 – 该值不会来“外的薄空气”。 它可能是陈旧的,对于对象,甚至可能long 64位值(取决于您的硬件架构),也可能会部分更新,但它不会有一些随机值。

在您的示例中, number初始化为0 ,然后由主线程设置为42 ,这是ReaderThread number的可能值。

编辑:

正如Voo和yshavit指出的那样, JLS第17.7节特别提到有一些架构可以实现64位操作,因为它可以被中断的2个独立的32位操作。 这意味着一个线程可能只看到另一半线程对字段的更新。 虽然不是“凭空”,但由于按位数字表示,结果值似乎是任何线程都没有设置的值。

“超薄空气安全”确实比例如“顺序一致性”要弱得多,但在某种意义上,人们可能称之为“强大”。 也就是说,如果您的系统提供OOTA安全性,您可以比系统没有提供的那样更好地推断您的程序。 如果您使用的系统不提供OOTA安全性,那么您基本上就是搞砸了。

汉斯·伯姆(Hans Boehm)和布莱恩·德姆斯基(Brian Demsky)最近撰写了一篇题为“禁止鬼魂:避免无法实现的结果”的论文(PDF格式见ACM.org)。 他们给出了非OOTA安全系统如何工作的一些非常好的例子,以及在这样的系统中可能出现的错误类型。

基本思想是允许某些系统进行推测性执行 ,包括对内存(缓存)的推测性存储,如果系统的推测结果不正确,则可以在以后撤消。 这在单线程系统中运行良好,但在multithreading系统中,两个线程的推测可能相互依赖 ,从而产生“失控的谣言” :处理器A认为x = 42,因为处理器B推测存储42那里,但处理器B只存储了x = 42,因为处理器A告诉它y = 17 ……但处理器A只是基于x = 42的假设进行推测!

 void rivera() { void lemon() { if (x == 42) { if (y == 17) { y = 17; x = 42; } } } } 

“非OOTA安全”系统可能会重写这些方法(在伪Python-Javascript语法中,抱歉)

 void rivera() { assert(y not in cache); // for the sake of argument cache.y = 17; // for the sake of speed, speculatively report that y=17 if (x not in cache) { cache.x = x; // meanwhile, slowly fetch the real value of x } if (cache.x != 42) { delete cache.y; // discard the earlier, speculative write } // and then later write cache.y back into the y in main memory } 

如果lemon()信任rivera()的推测报告cache.y = 17 ,反之亦然,你可以看到它将是一个巨大的问题。 在两种方法完成后,我们最终可能会遇到x=42y=17的情况,即使它们都没有这样开始!

我知道人们通常依靠时间旅行悖论隐喻来描述价值42和17如何“凭空捏造”在主存中,但我认为有线电视新闻比喻更为准确。 ;)