出于稀薄的空气安全

时间:2014-01-20 20:47:44

标签: java multithreading

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重新排序变量语句?

示例:根据重新排序: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;
    }
}

已编辑 - 已删除“请评论”评论。

2 个答案:

答案 0 :(得分:6)

  

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

是。 “Java Concurrency in Practice”中的引用试图指出您的number可能是042,具体取决于访问非同步字段所固有的竞争条件,但它不会(让我们说)1 - 价值不会“超薄”。它可能是陈旧的,对于对象,甚至可能long 64位值,具体取决于您的硬件架构,也可能部分更新,但它不会有一些随机值。< / p>

在您的示例中,number已初始化为0,然后由主线程设置为42,因此numberReaderThread的可能值为{{1}} }}

修改

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

答案 1 :(得分:3)

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

Hans Boehm和Brian Demsky最近撰写了一篇题为“禁止鬼魂:避免稀薄的结果”的论文(PDF available on ACM.org).他们给出了一些真正的好例子非OOTA安全系统可能会起作用,并且在这样的系统中可能会出现什么类型的错误。

基本思想是允许某些系统进行推测执行,包括对内存(缓存)的推测性存储,如果系统的推测结果不正确,可以在以后撤消。这在单线程系统中非常有用,但在多线程系统中,两个线程的推测可能相互依赖,从而产生“失控的谣言”:处理器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的情况,即使他们都没有这样开始!

我知道人们通常依靠time-travel paradox隐喻来描述价值42和17如何“凭空捏造”在主存中,但我认为有线新闻比喻更为准确。 ;)