类初始化死锁机制说明

时间:2018-12-08 11:45:09

标签: java multithreading jvm initialization deadlock

我在本地语言中从@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初始化。

下一步对我来说还不清楚。你能解释一下吗?

2 个答案:

答案 0 :(得分:2)

JVM Specification §5.5详细描述了类初始化过程。它由12个步骤组成。

在第6步中,将该类标记为“当前线程正在进行初始化”。

  
      
  1. 否则,记录当前线程正在进行C的Class对象初始化的事实,然后释放LC。
  2.   

因此Thread_1开始初始化类A并将其标记为由Thread_1初始化。类似地,类B被标记为由Thread_2初始化。

在步骤9,调用静态初始化程序。

  
      
  1. 接下来,执行C的类或接口初始化方法。
  2.   

A的静态初始化程序创建了B的实例,该实例尚未完全初始化,因此Thread_1(在初始化A的过程中)递归地启动B初始化过程。

在该过程的第2步中,它检测到类B正在通过不同的线程和块进行初始化。

  
      
  1. 如果C的Class对象指示其他线程正在对C进行初始化,则释放LC并阻塞当前线程,直到得知正在进行的初始化已完成,然后重复此过程。
  2. li>   

对称地Thread_2开始A的初始化,检测到它已经由其他线程初始化,并在步骤2处阻塞。两个线程都被阻塞,等待彼此。

注意:该类被标记为完全初始化,并在成功调用静态初始化程序后 通知其他线程,在这种情况下永远不会发生。

  
      
  1. 如果类或接口初始化方法的执行正常完成,则获取LC,将C的Class对象标记为完全初始化,通知所有等待的线程,释放LC,然后正常完成此过程。
  2.   

答案 1 :(得分:2)

  

线程_1正在创建A的实例

是的。并且new关键字要求对类A进行初始化。

  

线程_2(主线程)正在创建B的实例

是的。并且new关键字还要求初始化类B

但是clinitstatic块和static字段初始化)中的这些类之间存在依赖性。


来自JLS. 12.4.2. Detailed Initialization Procedure

  

对于每个类或接口C,都有一个唯一的初始化锁 LC。从C到LC的映射由Java虚拟机实现决定。然后,初始化C的过程如下:

     
      
  1. 为C同步初始化锁(LC)。这涉及等待直到当前线程可以获取LC
  2.   

(省略了其他步骤,但第一步是最重要的步骤)

所以这可能会发生:

Thread_1

  1. 看到它应该创建A类(new)的实例
  2. 开始类A的初始化(第一次使用A类)
  3. 获取A类的初始化锁。

(此处OS线程调度程序决定是时候挂起Thread_1并给main线程留些时间了)

还有main线程:

  1. 看到它应该创建B类(new)的实例
  2. 开始类B的初始化(第一次使用B类)
  3. 获取B类的初始化锁。

os线程调度程序现在挂起线程main并给Thread_1留出一些时间。

Thread_1继续:

  1. 看到static final B b = new B();
  2. 不允许创建新的B实例,因为B类尚未初始化。
  3. 它试图获取B类的初始化锁,但是该锁由main线程持有。

线程调度程序再次挂起Thread_1并给main留出一些时间。并继续:

  1. 看到static final A a = new A();
  2. 不允许创建新的A实例,因为B类尚未初始化。
  3. 它试图获取A类的初始化锁,但是该锁由Thread_1线程持有。

这是一个僵局:

  1. Thread_1拥有A初始化锁,并希望获取B类初始化锁
  2. main线程持有B初始化锁,并希望获取A类初始化锁