Class初始化死锁机制解释
Class initialization deadlock mechanism explanation
我用我的母语找到了一篇文章(https://habr.com/company/odnoklassniki/blog/255067/) from the @apangin(https://whosebug.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 个线程:
Thread_1 正在创建 A
的实例
Thread_2(主线程)正在创建 B
的实例
这是对 class 的第一次访问,因此它导致(可能导致)并发 classes A 和 B 初始化。
我不清楚下一步。你能解释一下吗?
JVM Specification §5.5 详细描述了 class 初始化过程。它由 12 个步骤组成。
在第 6 步,class 被标记为 "in progress of initialization by the current thread"。
- Otherwise, record the fact that initialization of the Class object for C is in progress by the current thread, and release LC.
所以Thread_1
开始初始化classA
并标记为被Thread_1
初始化。同样 class B
被标记为由 Thread_2
.
初始化
在第 9 步调用静态初始化程序。
- Next, execute the class or interface initialization method of C.
A
的静态初始化程序创建了一个 B
的实例,该实例尚未完全初始化,因此 Thread_1
(在初始化 A
的过程中)递归开始初始化 B
.
的过程
在程序的第 2 步,它检测到 class B
正在由不同的线程和块进行初始化。
- If the Class object for C indicates that initialization is in progress for C by some other thread, then release LC and block the current thread until informed that the in-progress initialization has completed, at which time repeat this procedure.
对称地Thread_2
开始A
的初始化,检测到它已经被不同的线程初始化并且也在第2步阻塞。两个线程都被阻塞等待对方。
注意:class 被标记为完全初始化并通知其他线程在 成功调用静态初始化程序后,在这种情况下不会发生这种情况。
- If the execution of the class or interface initialization method completes normally, then acquire LC, label the Class object for C as fully initialized, notify all waiting threads, release LC, and complete this procedure normally.
Thread_1 is creating instance of A
是的。而new
关键字需要classA
进行初始化。
Thread_2(main thread) is creating instance of B
是的。而new
关键字也需要classB
进行初始化
但是 clinit
中的这些 class 之间存在依赖关系(static
块和 static
字段初始化)。
来自 JLS. 12.4.2. Detailed Initialization Procedure:
For each class or interface C, there is a unique initialization lock LC. The mapping from C to LC is left to the discretion of the Java Virtual Machine implementation. The procedure for initializing C is then as follows:
- Synchronize on the initialization lock, LC, for C. This involves waiting until the current thread can acquire LC.
(其他步骤省略,但第most重要)
所以这是可能发生的事情:
Thread_1
:
- 看到它应该创建一个
A
class (new
) 的实例
- 开始 class
A
初始化(第一次 A
class 用法)
- 获取
A
class. 的初始化锁
(此处 OS 线程调度程序决定是时候暂停 Thread_1
并给 main
线程一些时间)
和 main
线程:
- 看到它应该创建一个
B
class (new
) 的实例
- 开始 class
B
初始化(第一次 B
class 用法)
- 获取
B
class. 的初始化锁
os 线程调度程序现在挂起线程 main
并给 Thread_1
一些时间。
Thread_1
继续:
- 看到
static final B b = new B();
- 不允许创建
B
的新实例,因为 B
class 尚未初始化。
- 它尝试获取
B
class 的初始化锁,但锁被 main
线程持有。
线程调度程序再次挂起 Thread_1
并给 main
一些时间。它继续:
- 看到
static final A a = new A();
- 不允许创建
A
的新实例,因为 B
class 尚未初始化。
- 它试图获取
A
class 的初始化锁,但是锁被 Thread_1
线程持有。
所以这是一个僵局:
Thread_1
持有A
初始化锁,想要获取B
class初始化锁
main
线程持有B
初始化锁,想要获取A
class初始化锁
我用我的母语找到了一篇文章(https://habr.com/company/odnoklassniki/blog/255067/) from the @apangin(https://whosebug.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 个线程:
Thread_1 正在创建 A
的实例Thread_2(主线程)正在创建 B
的实例这是对 class 的第一次访问,因此它导致(可能导致)并发 classes A 和 B 初始化。
我不清楚下一步。你能解释一下吗?
JVM Specification §5.5 详细描述了 class 初始化过程。它由 12 个步骤组成。
在第 6 步,class 被标记为 "in progress of initialization by the current thread"。
- Otherwise, record the fact that initialization of the Class object for C is in progress by the current thread, and release LC.
所以Thread_1
开始初始化classA
并标记为被Thread_1
初始化。同样 class B
被标记为由 Thread_2
.
在第 9 步调用静态初始化程序。
- Next, execute the class or interface initialization method of C.
A
的静态初始化程序创建了一个 B
的实例,该实例尚未完全初始化,因此 Thread_1
(在初始化 A
的过程中)递归开始初始化 B
.
在程序的第 2 步,它检测到 class B
正在由不同的线程和块进行初始化。
- If the Class object for C indicates that initialization is in progress for C by some other thread, then release LC and block the current thread until informed that the in-progress initialization has completed, at which time repeat this procedure.
对称地Thread_2
开始A
的初始化,检测到它已经被不同的线程初始化并且也在第2步阻塞。两个线程都被阻塞等待对方。
注意:class 被标记为完全初始化并通知其他线程在 成功调用静态初始化程序后,在这种情况下不会发生这种情况。
- If the execution of the class or interface initialization method completes normally, then acquire LC, label the Class object for C as fully initialized, notify all waiting threads, release LC, and complete this procedure normally.
Thread_1 is creating instance of A
是的。而new
关键字需要classA
进行初始化。
Thread_2(main thread) is creating instance of B
是的。而new
关键字也需要classB
进行初始化
但是 clinit
中的这些 class 之间存在依赖关系(static
块和 static
字段初始化)。
来自 JLS. 12.4.2. Detailed Initialization Procedure:
For each class or interface C, there is a unique initialization lock LC. The mapping from C to LC is left to the discretion of the Java Virtual Machine implementation. The procedure for initializing C is then as follows:
- Synchronize on the initialization lock, LC, for C. This involves waiting until the current thread can acquire LC.
(其他步骤省略,但第most重要)
所以这是可能发生的事情:
Thread_1
:
- 看到它应该创建一个
A
class (new
) 的实例
- 开始 class
A
初始化(第一次A
class 用法) - 获取
A
class. 的初始化锁
(此处 OS 线程调度程序决定是时候暂停 Thread_1
并给 main
线程一些时间)
和 main
线程:
- 看到它应该创建一个
B
class (new
) 的实例
- 开始 class
B
初始化(第一次B
class 用法) - 获取
B
class. 的初始化锁
os 线程调度程序现在挂起线程 main
并给 Thread_1
一些时间。
Thread_1
继续:
- 看到
static final B b = new B();
- 不允许创建
B
的新实例,因为B
class 尚未初始化。 - 它尝试获取
B
class 的初始化锁,但锁被main
线程持有。
线程调度程序再次挂起 Thread_1
并给 main
一些时间。它继续:
- 看到
static final A a = new A();
- 不允许创建
A
的新实例,因为B
class 尚未初始化。 - 它试图获取
A
class 的初始化锁,但是锁被Thread_1
线程持有。
所以这是一个僵局:
Thread_1
持有A
初始化锁,想要获取B
class初始化锁main
线程持有B
初始化锁,想要获取A
class初始化锁