实践中的Java并发书给出了不安全发布的一个例子
public class Holder
{
private int n;
public Holder(int n)
{
this.n = n;
}
public void assertSanity()
{
if (n != n)
throw new AssertionError("This statement is false.");
}
}
上面的代码似乎是线程安全的。如果n是公共变量,它将不是线程安全的。这本书的例子是错的吗?
答案 0 :(得分:10)
安全发布是关于内存可见性。内存可见性的概念比竞争条件等其他线程安全问题有点棘手。
当一个线程按特定顺序执行的操作似乎以不同的顺序执行另一个线程时,会出现内存可见性问题(可能是由编译器或CPU进行的优化引起的)。
在你的情况下:
// Thread A
h = new Holder(42);
// Thread B
h.assertSanity();
对于线程A,n
肯定会在h
之前初始化。
但是由于没有安全发布,因此线程B不能保证相同。线程B可能会在初始化状态下看到h
,但n
将不会被初始化。此外,在评估n
期间,线程B观察到的n != n
状态可能会发生变化,从而导致assertSanity()
抛出异常。
请注意,在所有情况下都不会发生此问题。您可能永远不会看到这种情况发生,但Java Memory Model在这种情况下并不保证正确性。
答案 1 :(得分:2)
它不是线程安全的,因为当创建类的对象时,Holder.n
被赋予默认值0
。
因此,某些线程可以看到值0
,而其他线程可以看到传递给构造函数的值n
。
答案 2 :(得分:0)
这门课很好。我们通常应该期望只有通过安全发布才能在线程之间共享对象。在这种情况下,Holder.n
对于任何观察者来说都是相同的常量值。
通过不安全的发布共享对象有时是合理的,但是程序员必须负责确保该对象可以在那种情况下使用,例如String
。
大多数课程不需要也不应该被设计为与不安全的出版物一起使用。