具有非final字段的不可变对象如何是线程不安全的?

说我们有这个

// This is trivially immutable. public class Foo { private String bar; public Foo(String bar) { this.bar = bar; } public String getBar() { return bar; } } 

是什么让这个线程不安全? 继这个问题之后 。

一旦安全发布, Foo就是线程安全的。 例如,这个程序可能会打印出“不安全”(它可能不会使用hotspot / x86的组合) – 如果你使bar最终它不会发生:

 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(); } } 

由于JVM优化,您永远不能假设操作按照它们的编写顺序执行,除非它对同一个线程很重要。 因此,当您调用构造函数然后将对结果对象的引用传递给另一个线程时,JVM可能实际上不会在同一个线程中需要之前写入foo.bar的值。

这意味着在multithreading环境中,可以在构造函数中的值写入之前调用getBar方法。

很可能你现在已经得到了答案,但只是为了确保我也想添加我的解释。

为了使对象(对于您的情况)是线程安全的,它必须:

  • 是不可改变的
  • 安全发布

永恒 – 你做到了。 设置后无法修改栏。 这里很明显。

安全发布 。 根据示例,代码未安全发布。 因为bar不是最终的,所以编译器可以根据需要自由重新排序。 在写入bar 之前 ,编译器可以发布(写入主存储器)对Foo实例的引用。 这意味着bar为null。 因此, 首先将对Foo的引用写入主存储器, 然后发生写入条形码。 在这两个事件之间,另一个线程可以将过时条视为null。

如果你添加final,JMM将保证:

保证最终字段的值对访问构造对象的其他线程可见。

或者,最终字段可防止重新排序。 因此,使变量最终将确保线程安全。

从评论中发布的链接 :

 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 = fx; // guaranteed to see 3 int j = fy; // could see 0 } } } 

一个线程可以调用writer() ,另一个线程可以调用reader() 。 reader()中的if条件可以求值为true,但是因为y不是最终的, 对象初始化可能还没有完全完成 (所以对象尚未安全发布),因此int j = 0可能会发生,因为它没有已初始化。