基于Thread.start()先行发生原则,第一种场景下AssertionError是否永不会抛出?
你的判断完全正确!咱们来仔细拆解两种场景下的线程安全逻辑,结合先行发生(happens-before)规则解释差异:
第一种main方法场景:永远不会抛出AssertionError
在第一个版本的main方法里,主线程先完整执行了t.initialize()——也就是把holder引用指向了一个已经完成初始化的Holder实例(new Holder(42)),之后才进入循环不断启动新线程调用t.holder.assertSanity()。
这里的核心是Thread.start()的先行发生保证:主线程在调用new Thread(...).start()之前的所有操作,都happens-before于该新线程中的任何操作。这意味着主线程对holder的赋值(以及Holder构造函数里对n的赋值),对后续启动的所有线程都是完全可见的。
所以每个新线程调用assertSanity()时,看到的n都是已经正确初始化的42,两次读取n的结果必然一致,永远不会触发n != n的断言错误。
第二种main方法场景:可能抛出AssertionError
修改后的main方法把t.initialize()放到了单独的线程里执行,同时主线程不断启动新线程去读取t.holder并调用assertSanity(),这直接打破了安全发布的前提:
- 初始化
holder的线程和读取holder的线程之间没有任何同步机制,也就没有对应的happens-before关系。 - 由于JVM的指令重排序和缓存可见性特性,
Holder的构造函数可能被重排序:先把holder的引用指向一块未完全初始化的内存(此时n还是默认值0),之后再执行this.n = n的赋值操作。 - 如果某个读取线程刚好在这个重排序的间隙执行
assertSanity(),第一次读取n拿到默认值0,第二次读取时n已经被赋值为42,就会出现0 != 42的情况,触发AssertionError。
这正是经典的不安全发布场景:没有通过正确的同步机制发布对象,导致其他线程看到对象的不完整状态。
内容的提问来源于stack exchange,提问作者katiex7
相关产品推荐
相关产品推荐

