如果在静态初始值设定项块中创建线程,则程序挂起

我遇到了一种情况,我的程序挂起,看起来像死锁。但是我尝试用jconsole和visualvm来解决这个问题,但他们没有检测到任何死锁。示例代码:

public class StaticInitializer {

private static int state = 10;

static {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            state = 11;
            System.out.println("Exit Thread");
        }
    });

    t1.start();

    try {
        t1.join();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    System.out.println("exiting static block");
}

public static void main(String...strings) {
    System.out.println(state);
}
}

当我在调试模式下执行此命令时,我可以看到控制达到@Override公共 void run() { state = 11;

但是一旦执行 state=11,它就会挂起/死锁。我在stackoverflow中查看了不同的帖子,我认为静态初始值设定项是线程安全的,但在这种情况下,jconsole应该报告这一点。关于主线程,jconsole说它处于等待状态,这很好。但是对于在静态初始值设定项块中创建的线程,jconsole 表示它处于 RUNNABLE 状态且未被阻塞。我很困惑,这里缺乏一些概念。请帮帮我。


答案 1

你不只是在开始另一个线程 - 你正在加入它。该新线程必须等待完全初始化才能继续,因为它正在尝试设置字段...并且初始化已在进行中,因此它会等待。但是,它将永远等待,因为该初始化正在等待新线程终止。经典死锁。StaticInitializerstate

有关类初始化所涉及的内容的详细信息,请参阅 Java 语言规范 12.4.2 节。重要的是,初始化线程将“拥有”的监视器,但线程将等待获取该监视器。StaticInitializer.class

换句话说,您的代码有点像这个非初始值设定项代码(异常处理省略)。

final Object foo = new Object();
synchronized (foo)
{
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (foo) {
                System.out.println("In the new thread!");
            }
        });
    t1.start();
    t1.join();
});

如果你能理解为什么代码会死锁,那么你的代码基本上是一样的。

寓意是不要在静态初始值设定项中做太多工作。


答案 2

类加载是 jvm 中的一个敏感时间。当类被初始化时,它们会持有一个内部 jvm 锁,该锁将暂停尝试使用同一类的任何其他线程。因此,生成的线程很可能在继续之前等待 StaticInitializer 类完全初始化。但是,您的 StaticInitializer 类正在等待线程完成,然后再完全初始化。因此,死锁。

当然,如果你真的想做这样的事情,辅助线程是多余的,因为你在启动它后立即加入它(所以你也可以直接执行该代码)。

更新:

我对为什么没有检测到死锁的猜测是因为它发生在比标准死锁检测代码工作级别低得多的水平上。该代码与正常的对象锁定一起工作,而这是深层jvm内部的东西。