当前位置:  开发笔记 > 编程语言 > 正文

同时访问公共领域.为什么可以观察到不一致的状态?

如何解决《同时访问公共领域.为什么可以观察到不一致的状态?》经验,为你挑选了1个好方法。

我正在阅读B. Goetz Java Concurrency在实践中,现在我正处于section 3.5安全的出版物中.他说:

// Unsafe publication
public Holder holder;
public void initialize() {
    holder = new Holder(42);
}

这种不正确的发布可能允许另一个线程观察部分构造的对象.

我不明白为什么可以观察到部分构造的子对象.假设构造函数Holder(int)不允许this转义.因此,构造的引用只能由调用者观察到.现在,正如JLS 17.7所述:

无论是否将它们实现为32位或64位值,对引用的写入和读取始终是原子的.

线程不可能观察到部分构造的对象.

我哪里错了?



1> yshavit..:

因此,构造的引用只能由调用者观察到.

这就是你的逻辑突破的地方,尽管这似乎是一个非常合理的说法.

首先要做的事情是:17.7提到的原子性只表示当你读取一个引用时,你会看到所有的前一个值(从其默认值开始null)或者所有后续值.你永远不会得到一个带有对应于值1的位的引用和一些对应于值2的位,这实际上会使它成为对JVM堆中随机位置的引用 - 这将是非常糟糕的!基本上他们说,"引用本身将为空,或指向内存中的有效位置." 但是那个记忆中,那就是事情变得奇怪的地方.

设置一个简单的例子

我会假设这个简单的持有人:

public class Holder {
    int value; // NOT final!
    public Holder(int value) { this.value = value; }
}

鉴于此,当你这样做时会发生什么holder = new Holder(42)

    JVM为新的Holder对象分配一些空间,其所有字段都有默认值(即value = 0)

    JVM调用Holder构造函数

    JVM设置.value为传入值(42).

    构造函数完成

    JVM返回对刚分配的对象的引用,并设置Holder.holder为此新引用

重新排序让生活变得艰难(但它也使程序变得快速!)

问题是另一个线程可以按任何顺序查看这些事件,因为它们之间没有同步点.这是因为构造函数没有任何特殊的同步或发生在语义之前(这是轻微的谎言,但稍后会更多).您可以在JLS 17.4.4中看到"synchronized-with"操作的完整列表; 请注意,构造函数没有任何内容.

因此,另一个线程可能会将这些操作命令为(1,3,2).这意味着如果在事件1和3之间排序了一些其他事件 - 例如,如果有人读Holder.holder.value入本地var - 那么他们将看到新分配的对象,但在构造函数运行之前显示其值:您会看到Holder.holder.value == 0.这被称为部分构造的对象,它可能非常混乱.

如果构造函数有多个步骤(设置多个字段,或设置然后更改字段),那么您可以看到这些步骤的任何顺序.几乎所有赌注都已关闭.哎呀!

构造函数和final字段

我在上面提到过,当我断言构造函数没有任何特殊的同步语义时,我撒了谎.假设你没有泄漏this,那就有一个例外:任何final字段保证被看作是在构造函数的末尾(参见JLS 17.5).

您可以将其视为步骤2和3之间存在某种同步点,但它适用于final字段.

它不适用于非最终字段

它不会传递给其他同步点.

但是,它扩展到您通过final字段访问的任何状态.因此,如果你有一个final List,并且你的构造函数初始化它然后添加一些值,那么所有线程都保证看到该列表至少具有它在构造函数末尾的状态,包括那些add调用.(如果在构造函数之后修改列表,没有同步,那么所有的赌注都会再次关闭.)

这就是为什么在我上面的例子中重要的value不是最终的.如果是,那么你将无法看到Holder.holder.value == 0.

推荐阅读
臭小子
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有