St.*_*rio 11 java multithreading
我正在阅读B. Goetz Java Concurrency在实践中,现在我正处于section 3.5安全的出版物中.他说:
// Unsafe publication
public Holder holder;
public void initialize() {
holder = new Holder(42);
}
Run Code Online (Sandbox Code Playgroud)
这种不正确的发布可能允许另一个线程观察部分构造的对象.
我不明白为什么可以观察到部分构造的子对象.假设构造函数Holder(int)不允许this转义.因此,构造的引用只能由调用者观察到.现在,正如JLS 17.7所述:
无论是否将它们实现为32位或64位值,对引用的写入和读取始终是原子的.
线程不可能观察到部分构造的对象.
我哪里错了?
ysh*_*vit 18
因此,构造的引用只能由调用者观察到.
这就是你的逻辑突破的地方,尽管这似乎是一个非常合理的说法.
首先要做的事情是:17.7提到的原子性只表示当你读取一个引用时,你会看到所有的前一个值(从其默认值开始null)或者所有后续值.你永远不会得到一个带有对应于值1的位的引用和一些对应于值2的位,这实际上会使它成为对JVM堆中随机位置的引用 - 这将是非常糟糕的!基本上他们说,"引用本身将为空,或指向内存中的有效位置." 但是在那个记忆中,那就是事情变得奇怪的地方.
我会假设这个简单的持有人:
public class Holder {
int value; // NOT final!
public Holder(int value) { this.value = value; }
}
Run Code Online (Sandbox Code Playgroud)
鉴于此,当你这样做时会发生什么holder = new Holder(42)?
value = 0)<new instance>.value为传入值(42).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<String>,并且你的构造函数初始化它然后添加一些值,那么所有线程都保证看到该列表至少具有它在构造函数末尾的状态,包括那些add调用.(如果在构造函数之后修改列表,没有同步,那么所有的赌注都会再次关闭.)这就是为什么在我上面的例子中重要的value不是最终的.如果是,那么你将无法看到Holder.holder.value == 0.