Java 具有非final字段的不可变对象怎么可能是线程不安全的?
说我们有这个Java 具有非final字段的不可变对象怎么可能是线程不安全的?,java,multithreading,concurrency,java-memory-model,Java,Multithreading,Concurrency,Java Memory Model,说我们有这个 // This is trivially immutable. public class Foo { private String bar; public Foo(String bar) { this.bar = bar; } public String getBar() { return bar; } } 是什么使这个线程不安全?从这里开始。由于JVM优化,您永远不能假设操作是按照写入的顺序执行的,除非这对
// This is trivially immutable.
public class Foo {
private String bar;
public Foo(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
}
是什么使这个线程不安全?从这里开始。由于JVM优化,您永远不能假设操作是按照写入的顺序执行的,除非这对同一线程很重要。因此,当您调用构造函数,然后将对结果对象的引用传递给另一个线程时,JVM可能不会在同一线程中需要foo.bar的值之前写入它
这意味着在多线程环境中,可以在将构造函数中的值写入getBar方法之前调用它。
Foo
一旦安全发布,它就是线程安全的。例如,此程序可能会打印“不安全”(可能不会使用hotspot/x86的组合)-如果将bar
final设置为final,则不会发生:
public class UnsafePublication {
static Foo foo;
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (foo == null) {}
if (!"abc".equals(foo.getBar())) System.out.println("unsafe");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
foo = new Foo("abc");
}
}).start();
}
}
从发布的评论中:
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x; // guaranteed to see 3
int j = f.y; // could see 0
}
}
}
一个线程可以调用
writer()
,另一个线程可以调用reader()
。reader()中的if条件可以计算为true,但由于y不是最终的,对象初始化可能尚未完全完成(因此该对象尚未安全发布),因此int j=0
可能会发生,因为它尚未初始化。很可能您现在已经得到了答案,但为了确保我想补充我的解释
为了使对象(对于您的案例)是线程安全的,它必须:
- 一成不变
- 安全出版
或者,最后一个字段阻止重新排序。因此,将该变量设为final将确保线程安全。该问题的答案不包含在该问题的答案中吗?特别是,read看到了你对我的问题的评论,我很好奇答案可能是重复的:@NoobUnChained绝对不是重复的。这里没有setter。@Jonsket向我提出了另一个问题,描述了final如何防止数据争用问题,但没有涉及可能出现的问题-线程看到由于JVM内存模型重新排序而部分初始化的对象,等等,在这种情况下,getBar()会是什么return?@NoobUnChained因为所有引用实例变量都有一个默认值
null
,应该是这样。如果String bar
还没有赋值,那么foo.getBar()
怎么办?@assylias但是foo.getBar()
可能仍然返回null
,不是吗?@Eugene,仅仅将字段设置为final并不能保证安全发布,相反,我们仍然必须确保在对象的构造函数完成之前,不要在另一个线程可以看到的地方写入对正在构造的对象的引用。所以在这里,如果我们将bar字段设为final,那么当thread2在foo的构造函数完成后更新foo时,thread1方法将保证看到bar的正确初始化值,即foo.getBar()将永远不会返回null.So。。最后一个赋值是原子的?@Eugene,在“最近的”JVM(包括64位字段)中,每个赋值都是原子的。@BrunoReis true。我的意思是:如果y也是final(在上面的示例中),这是否意味着这两个变量都以原子方式设置为相应的值?或者更简单一点:这是否意味着构造函数是原子线程安全的?没有其他线程可以看到过时的值x或/和y?@Eugene:如果对象仅在构造函数返回后才发布(即,您没有从构造函数内部将此传递给任何外部对象),那么所有最终字段都保证由每个获取该对象引用的线程初始化。