不安全的发布并发java
实践中的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是公共变量,它将不是线程安全的。 这本书的例子是错的吗?
安全发布是关于内存可见性的 。 内存可见性的概念比其他线程安全问题(如竞争条件)有点棘手。
当一个线程以特定顺序执行的操作看起来以不同的顺序执行另一个线程时,会出现内存可见性问题(可能是由编译器或CPU进行的优化引起的)。
在你的情况下:
// Thread A h = new Holder(42); // Thread B h.assertSanity();
对于线程A, n
肯定在h
之前初始化。
但是在没有安全发布的情况下,它不能保证对于线程B是相同的。线程B可能看到h
处于初始化状态,但是n
将不会被初始化。 此外,在评估n != n
期间,线程B观察到的n
的状态可能会发生变化,从而导致assertSanity()
抛出exception。
请注意,在所有情况下都不会发生此问题。 您可能永远不会看到这种情况发生,但Java Memory Model在这种情况下并不保证正确性。
它不是线程安全的,因为当创建类的对象时, Holder.n
被赋值为默认值0
。
因此,一些线程可以看到值0
,其他线程可以看到传递给构造函数的值n
。
这个class很好。 我们通常应该期望只有通过安全发布才能在线程之间共享对象。 在这种情况下, Holder.n
对任何观察者来说都是相同的常数值。
通过不安全的发布共享对象有时是合理的,但程序员必须负责确保在该情况下可以使用该对象,例如String
。
大多数类不需要也不应该被设计为使用不安全的发布。