首先关闭代码,来自JCIP列出http://jcip.net/listings/StuffIntoPublic.java和 http://jcip.net/listings/Holder.java
public class SafePublication {
public static void main(String[] args) throws InterruptedException {
// System.out.println(Thread.currentThread().getName());
StuffIntoPublic t = new StuffIntoPublic();
t.initialize();
while (true) {
new Thread(() -> { t.holder.assertSanity(); }).start();
}
}
}
//@author Brian Goetz and Tim Peierls
class StuffIntoPublic {
public Holder holder;
public void initialize() {
// System.out.println(Thread.currentThread().getName());
holder = new Holder(42);
}
}
//@author Brian Goetz and Tim Peierls
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.");
}
}
}
我说在这种情况下永远不会抛出AssertionError,因为Thread.start()在保证之前发生。被注释的两个System.out.printlns都打印main,这意味着主线程通过在while(true)循环中的线程上创建和调用start来生成所有后来的线程。
由于这是创建和初始化Holder的线程,因为之前发生的保证,所有后续线程都可以安全地成为完全可见的持有者。我是对的吗?
我甚至尝试运行此代码很长时间而且没有断言错误。
但是,如果主要看起来像下面那样,那么我相信它可能会出现AssertionError
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName());
StuffIntoPublic t = new StuffIntoPublic();
new Thread(() -> t.initialize() ).start();
while (true) {
new Thread(() -> { t.holder.assertSanity(); }).start();
}
}
答案 0 :(得分:1)
是的,这是安全的,因为Thread#start
保证在之前发生。更加罗嗦:对Thread#start
之前发生的任何变量的任何读/写(如果你愿意的话,我倾向于按照程序顺序认为 )也会在之前发生> em>该线程中的任何操作(它是run
方法)。
实际上,如果以前没有发生过(允许重新排序)并且程序执行允许那些潜在的重新排序,那么可能会发生破坏并抛出该错误。我甚至倾向于说和一个适用于弱内存模型的CPU(假设你使用英特尔,这是一个强大的内存模型)可能会增加这个机会,但我不确定。
因此,据我所知,这些操作将按以下顺序进行:首先,使用变量n
重新排序发布引用(之前没有发生过,所以这是允许)。 Thread1创建Holder
的实例。 Thread2看到发布的引用并调用该方法。它将变量n
读为zero
(请记住,重新排序已经发生并且n
尚未写入,因此默认值为zero
),因此它是!=
检查,但创建了Holder
的Thread1,在{/ em> Thread2之前将n
写为12
,例如再次读取它(在!=n
部分中)。所以这可能会失败。
使值final
可以解决这个问题,因为它会引入正确的内存障碍,或在规则之前发生。