我在本地语言中从@apangin(https://habr.com/company/odnoklassniki/blog/255067/)找到了文章(https://stackoverflow.com/users/3448419/apangin),但我听不懂。解释很简洁
让我们考虑一下该代码:
static class A {
static final B b = new B();
}
static class B {
static final A a = new A();
}
public static void main(String[] args) {
new Thread(A::new).start();
new B();
}
您可以尝试运行该代码。在我的电脑上,它导致死锁的概率为75%/
所以我们有2个线程:
线程_1正在创建A的实例
线程_2(主线程)正在创建B的实例
这是对类的首次访问,因此它可能导致并发类A和B初始化。
下一步对我来说还不清楚。你能解释一下吗?
答案 0 :(得分:2)
JVM Specification §5.5详细描述了类初始化过程。它由12个步骤组成。
在第6步中,将该类标记为“当前线程正在进行初始化”。
- 否则,记录当前线程正在进行C的Class对象初始化的事实,然后释放LC。
因此Thread_1
开始初始化类A
并将其标记为由Thread_1
初始化。类似地,类B
被标记为由Thread_2
初始化。
在步骤9,调用静态初始化程序。
- 接下来,执行C的类或接口初始化方法。
A
的静态初始化程序创建了B
的实例,该实例尚未完全初始化,因此Thread_1
(在初始化A
的过程中)递归地启动B
初始化过程。
在该过程的第2步中,它检测到类B
正在通过不同的线程和块进行初始化。
- 如果C的Class对象指示其他线程正在对C进行初始化,则释放LC并阻塞当前线程,直到得知正在进行的初始化已完成,然后重复此过程。
li>
对称地Thread_2
开始A
的初始化,检测到它已经由其他线程初始化,并在步骤2处阻塞。两个线程都被阻塞,等待彼此。
注意:该类被标记为完全初始化,并在成功调用静态初始化程序后 通知其他线程,在这种情况下永远不会发生。
- 如果类或接口初始化方法的执行正常完成,则获取LC,将C的Class对象标记为完全初始化,通知所有等待的线程,释放LC,然后正常完成此过程。
答案 1 :(得分:2)
线程_1正在创建A的实例
是的。并且new
关键字要求对类A
进行初始化。
线程_2(主线程)正在创建B的实例
是的。并且new
关键字还要求初始化类B
。
但是clinit
(static
块和static
字段初始化)中的这些类之间存在依赖性。
来自JLS. 12.4.2. Detailed Initialization Procedure:
对于每个类或接口C,都有一个唯一的初始化锁 LC。从C到LC的映射由Java虚拟机实现决定。然后,初始化C的过程如下:
- 为C同步初始化锁(LC)。这涉及等待直到当前线程可以获取LC 。
(省略了其他步骤,但第一步是最重要的步骤)
所以这可能会发生:
Thread_1
:
A
类(new
)的实例A
的初始化(第一次使用A
类)A
类的初始化锁。(此处OS线程调度程序决定是时候挂起Thread_1
并给main
线程留些时间了)
还有main
线程:
B
类(new
)的实例B
的初始化(第一次使用B
类)B
类的初始化锁。 os线程调度程序现在挂起线程main
并给Thread_1
留出一些时间。
Thread_1
继续:
static final B b = new B();
B
实例,因为B
类尚未初始化。 B
类的初始化锁,但是该锁由main
线程持有。线程调度程序再次挂起Thread_1
并给main
留出一些时间。并继续:
static final A a = new A();
A
实例,因为B
类尚未初始化。 A
类的初始化锁,但是该锁由Thread_1
线程持有。这是一个僵局:
Thread_1
拥有A
初始化锁,并希望获取B
类初始化锁main
线程持有B
初始化锁,并希望获取A
类初始化锁