如果在静态初始化程序块中创建了线程,程序将挂起
我遇到过我的程序挂起的情况,看起来像死锁。 但我试着用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 public void run(){state = 11;
但只要执行state = 11,它就会挂起/死锁。 我在stackoverflow中查看了不同的post,我认为静态初始化程序是线程安全的,但在这种情况下,jconsole应该报告这一点。 关于主线程,jconsole说处于等待状态,那很好。 但对于在静态初始化程序块中创建的线程,jconsole表示它处于RUNNABLE状态而未被阻止。 我很困惑,这里缺乏一些概念。 请帮帮我。
你不只是开始另一个线程 – 你正在加入它。 新线程必须等待StaticInitializer
完全初始化才能继续,因为它正在尝试设置state
字段……并且初始化已经在进行中,所以它等待。 但是,它将永远等待,因为初始化正在等待新线程终止。 经典僵局。
有关类初始化涉及的内容的详细信息,请参阅Java语言规范部分12.4.2 。 重要的是,初始化线程将“拥有” StaticInitializer.class
的监视器,但新线程将等待获取该监视器。
换句话说,您的代码有点像这个非初始化代码(exception处理省略)。
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(); });
如果你能理解为什么代码会死锁,那么你的代码基本相同。
道德并不是在静态初始化器中做太多工作。
classloading在jvm中是一个敏感的时间。 当初始化类时,它们会持有一个内部的jvm锁,它会暂停任何其他尝试使用同一个类的线程。 因此,您生成的线程很可能在继续之前等待StaticInitializer类完全初始化。 但是,您的StaticInitializer类在完全初始化之前正在等待线程完成。 因此,僵局。
当然,如果你真的想要做这样的事情,那么辅助线程是多余的,因为你在启动它之后就加入它(所以你不妨直接执行那个代码)。
更新:
我猜测为什么没有检测到死锁是因为它发生在比标准死锁检测代码工作的水平低得多的水平。 该代码适用于普通对象锁定,而这是深度jvm内部的东西。
通过注释掉行state = 11;
我能够让你的程序运行state = 11;
在完成初始化之前,不能设置state = 11。 在t1完成运行之前,您无法完成初始化。 在设置state = 11之前,T1无法完成运行。 僵局。
以下是我认为发生的事情:
- 主线程尝试初始化
StaticInitializer
。 这涉及锁定相应的Class
对象。 - 在仍然持有锁的同时,主线程产生另一个线程, 并等待它终止 。
- 另一个线程的
run()
方法尝试访问state
,这需要StaticInitializer
完全初始化; 这涉及等待与步骤1中相同的锁定。
最终结果:陷入僵局。
有关初始化过程的详细说明,请参阅JLS。
如果将t1.join()
移动到main()
,一切正常。