在这种情况下永远不会抛出这个 AssertionError 吗?
Will this AssertionError never be thrown in this case?
首先是代码,来自 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 的线程,因此由于发生前保证,所有后续线程都可以安全地成为完全可见的持有者。我说得对吗?
我什至尝试了 运行 这段代码很长时间,没有断言错误。
但是,如果 main 如下所示,那么我相信有可能出现 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();
}
}
是的,这是安全的,因为 Thread#start
保证 happens-before。更罗嗦:任何 read/write 发生在 Thread#start
之前的任何变量(如果你愿意,我倾向于认为程序顺序 高于 ),也会发生 在该线程中的任何操作之前(它是run
方法)。
确实,如果之前没有发生(允许重新排序)并且程序执行允许这些潜在的重新排序,那可能会中断并抛出该错误。我什至倾向于说 和 适当的 CPU 与弱内存模型一起使用(假设您使用的是英特尔,这是一种强内存模型)可能会增加这种机会,但我我不确定。
因此,据我所知,操作将按以下顺序进行:首先,发布引用是用变量 n
重新排序的(没有 happens-before,所以这是允许的)。 Thread1 创建了 Holder
的一个实例。 Thread2 看到已发布的引用并调用该方法。它读取变量 n
为 zero
(记住重新排序发生并且 n
尚未写入,因此具有默认值 zero
),因此它然后!=
检查 但是 创建 Holder
的线程 1,将 n
写入 12
例如 之前 Thread2 再次读取它(在 !=n
部分)。所以这可能会失败。
设置值 final
可以解决这个问题,因为它引入了正确的内存屏障,或者 happens-before 规则。
首先是代码,来自 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 的线程,因此由于发生前保证,所有后续线程都可以安全地成为完全可见的持有者。我说得对吗?
我什至尝试了 运行 这段代码很长时间,没有断言错误。
但是,如果 main 如下所示,那么我相信有可能出现 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();
}
}
是的,这是安全的,因为 Thread#start
保证 happens-before。更罗嗦:任何 read/write 发生在 Thread#start
之前的任何变量(如果你愿意,我倾向于认为程序顺序 高于 ),也会发生 在该线程中的任何操作之前(它是run
方法)。
确实,如果之前没有发生(允许重新排序)并且程序执行允许这些潜在的重新排序,那可能会中断并抛出该错误。我什至倾向于说 和 适当的 CPU 与弱内存模型一起使用(假设您使用的是英特尔,这是一种强内存模型)可能会增加这种机会,但我我不确定。
因此,据我所知,操作将按以下顺序进行:首先,发布引用是用变量 n
重新排序的(没有 happens-before,所以这是允许的)。 Thread1 创建了 Holder
的一个实例。 Thread2 看到已发布的引用并调用该方法。它读取变量 n
为 zero
(记住重新排序发生并且 n
尚未写入,因此具有默认值 zero
),因此它然后!=
检查 但是 创建 Holder
的线程 1,将 n
写入 12
例如 之前 Thread2 再次读取它(在 !=n
部分)。所以这可能会失败。
设置值 final
可以解决这个问题,因为它引入了正确的内存屏障,或者 happens-before 规则。