Java 线程如何查看安全初始化对象的过期引用
我一直试图弄清楚,安全发布的不可变对象是如何通过过时引用被观察到的Java 线程如何查看安全初始化对象的过期引用,java,multithreading,thread-safety,Java,Multithreading,Thread Safety,我一直试图弄清楚,安全发布的不可变对象是如何通过过时引用被观察到的 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(i
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类中。引用自
这意味着,如果两个线程同时更新同一对象的一个变量,并且该变量未声明为volatile,则可能存在这样的情况:其中一个线程的缓存中有一个旧值
根据您的代码,可能会发生以下情况:
- 线程1调用getHelper()并获取null
- 线程2调用getHelper()并获取null
- 线程1调用setHelper(42)
- 线程2调用setHelper(24)
volatile
至少可以解决缓存问题 引用
这意味着,如果两个线程同时更新同一对象的一个变量,并且该变量未声明为volatile,则可能存在这样的情况:其中一个线程的缓存中有一个旧值
根据您的代码,可能会发生以下情况:
- 线程1调用getHelper()并获取null
- 线程2调用getHelper()并获取null
- 线程1调用setHelper(42)
- 线程2调用setHelper(24)
在这种情况下,您的问题在于哪个线程将使用哪个Helper对象。关键字
volatile
至少可以解决缓存问题 多个线程同时读取变量helper
。至少,您必须使其易失性
,否则编译器将开始将其缓存在线程的本地寄存器中,并且对变量的任何更新可能不会反映在主内存中。使用volatile
,当线程开始读取共享变量时,它将清除其缓存并从全局内存中获取新值。当它完成读取时,它会将缓存中的内容刷新到主内存中,以便其他线程可以获得更新的值。多个线程同时读取变量helper
。至少,您必须使其易失性
,否则编译器将开始将其缓存在线程的本地寄存器中,并且对变量的任何更新可能不会反映在主内存中。使用volatile
,当线程开始读取共享变量时,它将清除其缓存并从全局内存中获取新值。当它完成读取时,它会将缓存中的内容刷新到主内存中,以便其他线程可以获得更新的值。发布对不可变对象的引用这一事实与此无关
如果您正在从多个线程读取引用的值,那么如果您关心使用最新值的所有线程,则需要确保写入发生在读取之前
before是语言规范中精确定义的术语,特别是关于Java内存模型的部分,它允许线程进行优化,例如,不总是更新主内存中的内容(速度很慢),而是将它们保存在本地缓存中(这要快得多,但可能会导致线程对“同一”变量持有不同的值)。之前发生的是一种关系,它可以帮助您推断在使用这些优化时多个线程如何交互
除非您确实创建了“发生在之前”关系,否则无法保证您会看到最新的值。在您显示的代码中,helper
的写入和读取之间没有这种关系,因此您的线程不能保证看到helper
的“新”值。它们可能会看到,但可能不会
确保写入发生在读取之前的最简单方法是使helper
成员变量final
:确保写入final
字段的值发生在构造函数结束之前,因此所有线程始终看到字段的正确值(前提是此
未在构造函数中泄漏)
显然,将其设置为最终版并不是一个选项,因为您有一个setter。因此您必须采用其他机制
从表面上看代码,最简单的选择是使用(最终)AtomicInteger
而不是Helper
类:写入AtomicInteger
肯定会在后续读取之前发生。但我想实际的Helper类可能更复杂
所以,你必须在建立关系之前先建立这种关系。实现这种关系的三种机制是:
- 使用
:这与AtomicReference
具有类似的语义,但允许您存储引用类型的值。(感谢您指出这一点,@Thilo)AtomicInteger
- 使字段
:这保证了最近写入的值的可见性,因为它会导致写入刷新到主存(而不是从线程缓存读取),并从主存读取。它有效地阻止JVM进行此特定优化易失性
- 访问同步块中的字段。最简单的方法是同步getter和setter方法。重要的是,您不应该在
上同步,因为此字段正在更改helper
- 发布对不可变对象的引用这一事实与此无关
如果您正在从多个线程读取引用的值,那么如果您关心所有线程使用最多的