【问题标题】:Program hangs if thread is created in static initializer block如果在静态初始化程序块中创建线程,程序将挂起
【发布时间】:2011-11-22 23:53:44
【问题描述】:

我遇到了我的程序挂起的情况,看起来像死锁。但是我尝试使用 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);
}
}

当我在调试模式下执行此操作时,我可以看到控制到达 @覆盖 公共无效运行(){ 状态 = 11;

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

【问题讨论】:

  • 很好的问题,为什么线程在 join() 期间可以运行。显然是 WAITING,所以这将是一个更合适的状态。
  • 实际上,javadoc 说它应该处于 WAITING 状态:download.oracle.com/javase/6/docs/api/java/lang/…
  • 这很有趣。我来到这里是因为我使用 scala 工作表进行了一些编程,mail.google.com/mail/u/0/#inbox/1472a9cccdc4ffbd 这是一个非常酷的范例,它允许在您键入时在线显示计算结果。然而,这意味着所有工作都在静态初始化程序中完成。你能谴责吗?

标签: java multithreading deadlock static-initializer


【解决方案1】:

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

请参阅Java Language Specification section 12.4.2,了解有关类初始化的详细信息。重要的是,初始化线程将“拥有”StaticInitializer.class 的监视器,但 new 线程将等待获取该监视器。

换句话说,您的代码有点像这种非初始化代码(省略了异常处理)。

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();
});

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

建议不要在静态初始化器中做太多工作。

【讨论】:

  • 嗨,乔恩,首先谢谢。但我想知道产生的线程的状态是什么?为什么 jconsole 没有检测到死锁?
  • 如果没有必要,不要使用线程。没有并发编程 -> 没有死锁。
  • @thomas:新线程将等待获取 StaticInitializer.class (BLOCKED) 上的锁。原始线程将等待加入新线程(WAITING)。我不知道为什么 jconsole 没有检测到它。
  • @emory:嗯,我不确定——根据我的经验,值得使用多线程的情况要多于值得在类初始化器中做大量工作的情况。
  • @Jon, 我不知道为什么 jconsole 没有检测到它。 jstack/jconsole 可以看到正在等待(或同步)或通过@停放的对象987654326@。类初始化不使用任何这些。
【解决方案2】:

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

当然,如果你真的想做这样的事情,辅助线程是多余的,因为你在启动它之后就加入了它(所以你还不如直接执行那个代码)。

更新:

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

【讨论】:

  • t1 是多余的,但你可以扩展它。为什么不将状态初始化为 11。由于 t1 是多余的,因此无需打印“退出线程”。整个事情可以用“private static int state = 11;”来完成
  • @emory - 当然,我同意。我认为 OP 正在简化一些更有趣的场景。
  • 感谢 jtahlborn。我同意你的理由。但是关于衍生线程:为什么它的状态不是等待或阻塞而是可运行的?
  • @thomas - 在另一篇帖子的问题中对此发表了评论。
【解决方案3】:

我可以通过注释掉state = 11;这一行来让你的程序运行

在完成初始化之前,您不能设置 state=11。在 t1 完成运行之前,您无法完成初始化。在设置 state=11 之前,T1 无法完成运行。死锁。

【讨论】:

  • 感谢埃默里。我也猜到这是僵局。但是我应该在线程转储和 jconsole 线程状态中对衍生线程的状态有什么期望?
【解决方案4】:

这是我认为会发生的事情:

  1. 主线程尝试初始化StaticInitializer。这涉及锁定相应的Class 对象。
  2. 在仍持有锁的同时,主线程生成另一个线程,并等待它终止
  3. 另一个线程的run()方法尝试访问state,这需要StaticInitializer完全初始化;这涉及等待与第 1 步相同的锁。

最终结果:死锁。

请参阅 JLS 以获取 detailed description of the intialization procedure

如果您将t1.join() 移动到main(),一切正常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-23
    • 1970-01-01
    相关资源
    最近更新 更多