【发布时间】:2021-06-13 09:57:37
【问题描述】:
来自Java Concurrency In Practice一书:
为了安全地发布对象,对象的引用和对象的状态必须同时对其他线程可见。正确构造的对象可以通过以下方式安全发布:
- 从静态初始化器初始化对象引用;
- 将对其的引用存储到 volatile 字段或 AtomicReference 中;
- 将对它的引用存储到正确构造的对象的最终字段中;或
- 将对它的引用存储到由锁适当保护的字段中。
我的问题是:
为什么要点 3 有约束:“of a properConstructed object”,但要点 2 没有?
以下代码是否安全地发布了map 实例?我认为代码符合要点2的条件。
public class SafePublish {
volatile DummyMap map = new DummyMap();
SafePublish() throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
// Safe to use 'map'?
System.out.println(SafePublish.this.map);
}
}).start();
Thread.sleep(5000);
}
public static void main(String[] args) throws InterruptedException {
SafePublish safePublishInstance = new SafePublish();
}
public class DummyMap {
DummyMap() {
System.out.println("DummyClass constructing");
}
}
}
以下调试快照图片显示map 实例在执行时为null,正在进入SafePublish 的构造。如果另一个线程现在试图读取map 引用会发生什么?阅读安全吗?
【问题讨论】:
-
仅供参考:“这里打印 null”不是因为“正确构造的对象”的线程安全问题。这纯粹是因为
this.map还没有被赋值,因为这要等到 5 秒后才会发生。 -
@Andreas 有两个“正确构造的对象”,第一个是正在发布的对象,第二个是其字段为已发布对象的对象。请仔细阅读。
-
没关系。正如我已经说过的,该字段为
null的事实与线程安全无关。该字段为null,因为尚未分配该字段。代码如何打印除 null 之外的任何内容?那会是什么非空值? -
因为这是条件。但是这个问题是没有意义的,因为你在问为什么代码不是线程安全的,如“由你的代码显示”,但代码没有显示,它只是显示一个尚未分配的字段默认值为
null。该代码不能证明任何事情,而是根据您的有缺陷的结论提出问题。 -
@Jason 我不知道您所说的“
SafePublish的构造”是什么意思。如果你反编译构造函数,你会看到编译器把它改成了SafePublish() { super(); this.map = new DummyMap(); new Thread(...,所以你可以看到,map被赋值之前你在构造函数中写的代码,但它确实发生了执行期间构造函数执行作为一个整体。
标签: java concurrency volatile java-memory-model safe-publication