java - 线程如何看到安全初始化对象的过时引用
问题描述
我一直在试图弄清楚如何通过陈旧的引用观察到安全发布的不可变对象。
public final class Helper {
private final int n;
public Helper(int n) {
this.n = n;
}
}
class Foo {
private Helper helper;
public Helper getHelper() {
return helper;
}
public void setHelper(int num) {
helper = new Helper(num);
}
}
到目前为止,我可以理解 Helper 是不可变的并且可以安全地发布。读取线程读取 null 或完全初始化的 Helper 对象,因为它在完全构造之前不可用。解决方案是将 volatile 放在我不明白的 Foo 类中。
解决方案
您发布对不可变对象的引用这一事实在这里无关紧要。
如果您正在从多个线程读取引用的值,如果您关心使用最新值的所有线程,则需要确保写入发生在读取之前。
Happens before是语言规范中精确定义的术语,特别是关于 Java 内存模型的部分,它允许线程进行优化,例如通过不总是更新主内存中的内容(这很慢),而是将它们保存在本地缓存(这要快得多,但可能导致线程为“相同”变量保存不同的值)。Happens-before 是一种关系,可帮助您推理在使用这些优化时多个线程如何交互。
除非您实际上创建了先发生关系,否则无法保证您会看到最新的值。在您显示的代码中, 的写入和读取之间没有这种关系helper
,因此您的线程不能保证看到 的“新”值helper
。他们可能会,但他们可能不会。
确保写入发生在读取之前的最简单方法是创建helper
成员变量final
:对字段值的写入final
保证在构造函数结束之前发生,因此所有线程始终看到字段的正确值(提供this
的没有在构造函数中泄漏)。
显然,在这里制作它final
不是一个选项,因为你有一个二传手。所以你必须使用一些其他的机制。
从表面上看代码,最简单的选择是使用(final)AtomicInteger
而不是Helper
类:写入AtomicInteger
保证在后续读取之前发生。但我猜你的实际助手类可能更复杂。
所以,你必须自己创造这种事前发生的关系。三个机制是:
- 使用
AtomicReference<Helper>
: 这与 具有相似的语义AtomicInteger
,但允许您存储引用类型的值。(感谢您指出这一点,@Thilo)。 - 制作字段
volatile
:这保证了最近写入的值的可见性,因为它会导致写入刷新到主内存(而不是从线程的缓存中读取),并从主内存中读取。它有效地阻止了 JVM 进行这种特定的优化。 - 访问同步块中的字段。最简单的做法是使 getter 和 setter 方法同步。值得注意的是,您不应该在 上同步
helper
,因为该字段正在更改。
推荐阅读
- css - 背景图像不重复动态添加的内容
- javascript - 无法将来自套接字 io 的数据作为道具传递给反应组件
- apache-spark - 有条件地获取上一行的值
- javascript - 为什么我不能在angularjs中以两种方式绑定指令组件具有相同的名称?
- python - 如何使用 multiprocessing.pool 传递一个熊猫系列、一个列表和两个常量字符串作为多处理的参数?
- woocommerce - 如果产品在 woocommerce 中有标签,请添加另一个电子邮件收件人
- google-sheets - 谷歌表格使用 arrayFormula 和间接通过地址读取单元格值
- javascript - 将json响应转换为键和值数组javascript
- python - 无法让 PyInstaller 查看已安装软件包的模块
- javascript - 在数值数组的 FilterExpression 中使用 IN 运算符