Java 如果在静态初始值设定项块中创建线程,则程序挂起
我遇到了一种情况,我的程序挂起,看起来像死锁。但是我试着用jconsole和visualvm解决了这个问题,但是他们没有检测到任何死锁。示例代码:Java 如果在静态初始值设定项块中创建线程,则程序挂起,java,multithreading,deadlock,static-initializer,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() {
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说它处于可运行状态且未被阻止。我感到困惑,这里缺少一些概念。请帮助我解决问题。类加载在jvm中是一个敏感的时间段。初始化类时,类持有一个内部jvm锁,该锁将暂停尝试使用同一类的任何其他线程。因此,生成的线程它很可能在继续之前等待StaticInitializer类完全初始化。但是,您的StaticInitializer类在完全初始化之前等待线程完成。因此,死锁 当然,如果您真的想做这样的事情,那么辅助线程是多余的,因为您在启动它之后就加入了它(因此您最好直接执行该代码) 更新:
我猜测为什么没有检测到死锁,是因为它发生的级别远低于标准死锁检测代码的工作级别。该代码与普通对象锁定一起工作,而这是jvm内部的深层内容。我能够通过注释掉行
state=11;
在完成初始化之前,您无法设置state=11。在t1完成运行之前,您无法完成初始化。在设置state=11之前,t1无法完成运行。死锁。您不仅仅是在启动另一个线程,而是在加入它。新线程必须等待StaticInitializer
完全初始化因为它正在尝试设置状态
字段…并且初始化已经在进行中,所以它会等待。但是,它将永远等待,因为初始化正在等待新线程终止。经典死锁
有关类初始化所涉及内容的详细信息,请参阅。重要的是,初始化线程将“拥有”StaticInitializer.class的监视器,但新线程将等待获取该监视器
换句话说,您的代码有点像这个非初始值设定项代码(省略了异常处理)
如果您能够理解为什么代码会死锁,那么您的代码基本上也是一样的
寓意是不要在静态初始值设定项中做太多工作。以下是我认为会发生的情况:
静态初始值设定项
。这涉及到锁定相应的类
对象run()
方法尝试访问state
,这要求StaticInitializer
完全初始化;这需要等待与步骤1相同的锁如果您将
t1.join()
移动到main()
,一切都会正常工作。t1是多余的,这是一个很好的观点,但您可以扩展它。为什么不将状态初始化为11。t1是多余的,不需要打印“退出线程”。整个过程可以通过“私有静态int state=11”来完成@埃默里-当然,我同意。我认为OP是简化了一些更有趣的场景。谢谢jtahlborn。我同意你的任何推理。但是关于衍生线程:为什么它的状态不是等待或blcked,而是可运行的?@thomas-在你的另一篇帖子的问题中对此进行了评论。嗨,Jon,首先谢谢。但我想知道会发生什么ld是生成的线程的状态?为什么jconsole没有检测到死锁?一个更强烈的寓意是,如果不需要,就不要使用线程。无并发编程->无死锁。@thomas:新线程将等待获取StaticInitializer.class(已阻止)上的锁。原始线程将等待加入新线程(等待)。我不知道为什么jconsole没有检测到它。@emory:嗯,我不确定-根据我的经验,值得使用多线程的情况比值得在类初始值设定项中做大量工作的情况要多。@Jon,我不知道为什么jconsole没有检测到它。jstack/jconsole可以看到正在等待的对象(或同步)或通过java.util.concurrent.locks.LockSupport.park(对象)停止
。类初始化没有使用任何这些。谢谢emory。我也猜这是死锁。但是在线程转储和jconsole线程状态中,对于生成的线程的状态,我应该期待什么呢?问得好,为什么在join()期间线程是可运行的。它显然是在等待,所以这将是一个更合适的状态。实际上,javadoc说它应该处于等待状态:这很有趣。我来这里是因为我用scala工作表进行了一些编程,这是一个非常酷的范例,允许计算结果在键入时在线显示。但这意味着所有工作都是在静态初始化器。你能谴责它吗?
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();
});