如果在静态初始化程序块中创建了线程,程序将挂起

tho*_*mas 20 java multithreading deadlock static-initializer

我遇到过我的程序挂起的情况,看起来像死锁.但我尝试用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);
}
}
Run Code Online (Sandbox Code Playgroud)

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

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

Jon*_*eet 33

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

有关类初始化所涉及的内容的详细信息,请参阅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();
});
Run Code Online (Sandbox Code Playgroud)

如果你能理解为什么代码会死锁,它基本上为您的代码相同.

道德并不是在静态初始化器中做太多工作.

  • @emory:嗯,我不确定 - 根据我的经验,有更多的情况需要使用多个线程,而不是在类初始化器中值得做大量工作的情况. (3认同)
  • @Jon,*我不知道为什么jconsole没有检测到它.*jstack/jconsole可以看到等待(或同步)或通过`java.util.concurrent.locks.LockSupport.park(对象)停放的对象)`.类初始化不使用任何这些. (2认同)

jta*_*orn 13

classloading在jvm中是一个敏感的时间.当初始化类时,它们会持有一个内部的jvm锁,它会暂停任何其他尝试使用同一个类的线程.因此,您的衍生线程很可能在继续之前等待StaticInitializer类完全初始化.但是,您的StaticInitializer类在完全初始化之前正在等待线程完成.因此,僵局.

当然,如果你真的想要做这样的事情,那么辅助线程是多余的,因为你在启动它之后立即加入它(所以你不妨直接执行那些代码).

更新:

我猜测为什么没有检测到死锁是因为它发生在比标准死锁检测代码工作的水平低得多的水平.该代码适用于普通对象锁定,而这是深度jvm内部的东西.

  • @emory - 当然,我同意.我认为OP正在简化一些更有趣的场景. (2认同)

emo*_*ory 5

我能够通过评论出来让你的程序运行 state = 11;

在完成初始化之前,不能设置state = 11.在t1完成运行之前,您无法完成初始化.在设置state = 11之前,T1无法完成运行.僵局.