考虑以下玩具示例类:
public class Test {
private volatile Outer outerVar = new Outer();
static class Outer {
Inner innerVar = new Inner();
}
static class Inner {
// state
// setters
// getters
}
private void multithreadedUse() {
// play with outerVar.innerVar
}
}
outerVar是易失性的,因此可能正在使用它的所有线程都会看到它处于相同的状态。但是outerVar.innerVar怎么样?它的父对象(outerVar)被标记为volatile的事实是否也使它变得易变?
或者我们必须明确声明innerVar volatile吗?
答案 0 :(得分:6)
但是outerVar.innerVar怎么样?它的父对象(outerVar)被标记为volatile的事实是否也使它变得易变?
在此示例中,outerVar.innerVar
将正确发布,但不会出现波动。如果您稍后指定outerVar.innerVar = new Inner()
,则会丢失线程安全的发布。
这里的规则是在易失性写入之后发生的所有写入在易失性写入之后都是可见的。在写完之后,所有正常写入现在都是线程不安全的。
因此,在您的示例中,线程的排序会看到与
类似的内容volatile Outer outerVar;
Outer temp = new Outer();
temp.innerVal = new Inner()
outerVar = temp;
注意outVar = temp
的易失性写入。这是同步开始的地方。当另一个线程读取非null的outerVar实例时,将安全地发布innerVar字段。
重申一下,每当你为outerVar.innerVal
分配一个新值时,你都会失去同步。同样,如果innerVal
有任何字段[在初始易失写入后],那些字段的写入将无法正确同步
所以回答你的问题
Java的volatile关键字是“递归的”关于引用树,或者 必须将每个引用声明为volatile吗?
每个字段必须声明为volatile,在初始易失性写入(技术上讲)之后会发生变化。话虽如此,如果你在线程之间共享,你应该声明volatile或final字段。
答案 1 :(得分:1)
您对Volatile的理解是正确的,并且您对外类对象的期望也是正确的。但是,外类的属性不能免受线程安全的影响,这确实会破坏volatile的目的。修复您的情况的最佳方法是使内部变量不可变。这是易变的布尔包装器和字符串的工作方式。使内部状态不可变也是最终的,使得易失性对象线程安全,因此可以自由使用。
但是你仍然希望坚持你的代码,那么只要你在实例化后不改变Outer类的属性,你就是好的。但这基本上也是最终和不变性的意思。
查看此link有关线程安全的基础知识。