在我正在使用的图书馆深处,有两个相互引用的类 - 称之为MR1
和MR2
- 这样MR1.<clinit>
会导致MR2
成为MR2.<clinit>
classloaded和MR1
导致Util
被分类。
该库还有一个引用MR1
的{{1}}类,因此Util.<clinit>
会导致MR1
被分类。
最后,它有一个顶级类 - 称之为TopLevel
- 其构造函数有如下代码:
Util.callStaticUtilMethod();
MR2.callStaticMR2Method();
所以整体依赖图是这样的:
我最近遇到了一个类加载器死锁,其中两个线程碰巧同时实例化了TopLevel
,而其他类都没有被加载。一个帖子卡在MR1.<clinit>
内Util.<clinit>
内,准备调用Util.callStaticUtilMethod()
;另一个成功通过Util.callStaticUtilMethod()
,并被卡在MR2.<clinit>
,准备调用MR2.callStaticMR2Method()
。 (然后一堆其他线程卡在MR2.callStaticMR2Method()
线上。)
我不明白的是 - 如果一个线程仍然卡住,死锁,在Util.<clinit>
内,那么其他所有线程如何能够通过Util.callStaticUtilMethod()
来电?是否可以在课程完全初始化之前使用它?如果是这样,那么这会走多远;例如,一个线程在被另一个线程初始化之前是否可以访问静态最终字段? (从其他线程无法通过MR2.callStaticMR2Method()
电话的事实来看,似乎这个并非完全免费,幸运的是,幸运的是;但我无法说出规则可能是什么。)
答案 0 :(得分:0)
JLS(§12.4.1)声明静态方法调用将触发初始化:
类或接口类型T将在...之前立即初始化...调用由T声明的静态方法。
继续(§12.4.2)来描述线程触发初始化时会发生什么:
如果......某些其他线程正在对C进行初始化,则...阻止当前线程,直到通知正在进行的初始化已完成....否则,记录类初始化的事实当前线程正在进行C的对象....如果初始化程序的执行正常完成...将C的Class对象标记为完全初始化,[并]通知所有等待的线程。
理论上 你所描述的是不可能的。然而,这个有点模糊的脚注:
在某些情况下,编译时分析可以消除许多已从生成的代码初始化类型的检查。但是,这种分析必须完全考虑并发性和初始化代码不受限制的事实。
正如我所解释的那样,它可以让编译器自由地避免阻止静态方法调用,这些调用不依赖于任何静止初始化状态或以其他方式影响内存模型。
那就是说,我试图复制行为(在类初始化程序执行之前返回的静态方法)并且还没有能够这样做。你不应该排除你错过阅读日志的可能性。