【问题标题】:Out of Thin Air Safety无中生有的安全
【发布时间】:2014-01-20 20:47:43
【问题描述】:

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

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

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

也许提到这个 sn-p,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;
    }
}

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

【问题讨论】:

  • 请评论不是开始 SO 问题的好方法。您有具体问题需要我们回答吗?
  • 对我来说似乎离题了? “您应该只根据您所面临的实际问题提出实用、可回答的问题。喋喋不休的开放式问题会降低我们网站的实用性,并将其他问题推到首页之外。”
  • @Keppil - 好点,谢谢。我将删除该部分并保留关于属性强度的原始问题。
  • @KevinMeredith 抱歉,“低度数”到底是什么意思?我用谷歌搜索了这个词,没有找到任何东西。这是一个标准的、定义明确的术语,还是只是一个主观的衡量标准?因为如果是后者,在不知道你的主观线在哪里的情况下很难回答这个问题。
  • @yshavit,虽然我没有明确说明,但格雷的回答满足了我措辞不佳的问题。基本上我想更多地了解out of thin air safety 的含义。

标签: java multithreading


【解决方案1】:

“无中生有的安全”确实比例如“顺序一致性”,但在某种意义上可以称之为“强”。也就是说,如果您的系统提供了 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 如何“凭空”出现在主内存中,但我认为有线新闻的隐喻更准确。 ;)

【讨论】:

    【解决方案2】:

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

    是的。 “Java Concurrency in Practice”中的引用试图指出您的number 可能是042,这取决于访问非同步字段所固有的竞争条件,但它不会是(比如说)@987654327 @ -- 值不会“凭空出现”。它可能是陈旧的,对于对象,甚至可能是 long 64 位值,具体取决于您的硬件架构,也可能会部分更新,但不会有一些随机值。

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

    编辑:

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

    【讨论】:

    • 我实际上不同意这种说法,因为正如您指出的那样,doubleslongs 不能保证自动更新(除非声明为 volatile,但这一点没有实际意义)。的确,它并不是真正的“随机”,但从实际的角度来看,那里并没有太大的区别——它仍然是一个从未设置过的值,在给定的情况下可能没有意义。 (无论如何“至少它看到了一个实际放置在那里的值”实际上是错误的!)
    • 是的,我知道这不是凭空出现的,而是将00xffffffffff 写入一个字段,然后从中读取0xff,对于大多数实际目的来说,与任何其他任意值。 The JLS specifies writes/reads to doubles/longs as 2 separate 32-bit read/writes。它明确提到这可能导致线程只看到一半的值,所以理论上这是一个问题。我猜可能有一些 32 位架构不允许 64 位读/写,但我想不出。
    • @Gray JLS 明确允许它:17.7 部分。我不知道这是否真的发生在实践中;但这不是我想赌的东西!
    • 同意的家伙。我已经添加到我的答案中。谢谢。
    • 这个解释不正确。凭空产生的价值与撕裂的读/写无关。有关 OOTA 的一些示例,请查看“JSR-133”Java 内存模型和线程规范”第 19 页。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-20
    • 2013-11-10
    相关资源
    最近更新 更多