说我们有这个
// This is trivially immutable.
public class Foo {
private String bar;
public Foo(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
}
是什么让这个帖子不安全?继续question。
答案 0 :(得分:11)
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();
}
}
答案 1 :(得分:7)
由于JVM优化,您永远不能假设操作按照它们的编写顺序执行,除非它对同一个线程很重要。因此,当您调用构造函数然后将对结果对象的引用传递给另一个线程时,JVM可能实际上不会在需要在同一个线程之前写入foo.bar的值。
这意味着在多线程环境中,可以在构造函数中的值写入之前调用getBar方法。
答案 2 :(得分:5)
很可能你现在已经得到了答案,但只是为了确保我也想添加我的解释。
为了使对象(对于您的情况)是线程安全的,它必须:
永恒不变 - 你做到了。设置后无法修改栏。这里很明显。
安全发布。根据示例,代码未安全发布。因为bar不是final,所以编译器可以根据需要自由重新排序。编译器可以发布(写入主存储器)对Foo实例的引用,之前写入bar。这意味着bar为null。因此,首先对Foo的引用被写入主存储器,然后然后就会发生写入条形码。在这两个事件之间,另一个线程可以将陈旧栏视为空。
如果你添加final,JMM将保证:
保证最终字段的值对访问构造对象的其他线程可见。
或者,最终字段会阻止重新排序。因此,使变量最终将确保线程安全。
答案 3 :(得分:1)
来自评论中发布的link:
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,但是因为对象初始化可能没有完全完成(因此对象尚未安全发布),因此{{1可能会发生,因为它尚未初始化。