在 java 代码中出现死锁

Getting deadlock in java code

我有以下三个类。

BaseClass.java

public class BaseClass {

    static {
        load();
    }

    public static void init() {
        System.out.println("base init");
    }

    private static void load() {
        System.out.println("In load method of base class");
        DerivedClass dc = new DerivedClass();
        System.out.println("Object creation done.");
    }

}

DerivedClass.java

public class DerivedClass extends BaseClass {

    public DerivedClass() {
        System.out.println("derived class constructor");
    }

    public static boolean isSynthetic(String _attr) {
        return true;
    }
}

Helper.java

public class Helper {

    public static void main(String[] args) {
        Thread t = new Thread() {
            public void run() {
                BaseClass.init();
            };
        };
        t.start();
        System.out.println("calling static method of derived class..");
        System.out.println(DerivedClass.isSynthetic("test"));
    }

}

当我从 Helper.java 执行 main 方法时,我得到以下输出 -

calling static method of derived class..

In load method of base class

此执行停止后,但进程仍在 运行。 所以似乎有一些僵局,但我不明白为什么会这样。 需要帮助。

当第一次引用 BaseClass 时,class 加载程序启动并想要设置 class 以供使用。所以它加载 class 并启动静态初始化块

static {
    load();
}

这会调用 load 方法,然后您会尝试创建类型为 DerivedClass 的对象。这将首先尝试调用 super() 构造函数,即 class BaseClass 的方法 - 但 BaseClass 尚未完全初始化,因为它的静态初始化程序尚未完成 = > 僵局。

编辑: 根据您的评论,我做了更多的研究。其实,事情并没有我想的那么简单。 JVM 能够处理递归初始化,因此在单线程情况下没有问题。 class-initialization-process 的描述可以在 JVM 规范的第 5.5 节中找到。

这里的罪魁祸首实际上是两个初始化进程之间的竞争条件。

线程1到达DerivedClass.isSynthetic("test"),开始初始化DerivedClass

同时线程 2 到达 BaseClass.init() 并开始初始化 BaseClass

在初始化 DerivedClass 时,线程 1 认识到它必须初始化 superclass。由于线程 2 已经在进行 BaseClass 的初始化,因此线程 1 必须等待它完成。

初始化时 BaseClass 线程 2 到达 DerivedClass dc = new DerivedClass();。由于 DerivedClass 的初始化已经由线程 1 进行,线程 2 必须等待它完成。

所以实际上这是一个 class 逻辑死锁,其中两个线程尝试以不同的顺序(BaseClass->DerivedClass 与 DerivedClass->BaseClass)进入两个关键代码路径("initialization of class X")并结束起来等待对方。

在适当的地方添加一些 Thread.sleep(100); 也会告诉你这确实是一个竞争条件。在我的测试中,有时程序会成功完成,尽管初始化期间存在循环依赖。