Java:如何修复挂起的线程?

时间:2014-08-29 02:35:23

标签: java multithreading concurrency deadlock jclouds

请注意:我正在使用JClouds对此进行标记,因为如果您阅读了整个问题和随后发表的评论,我认为这可能是JClouds的错误或滥用该库

我有一个可执行的JAR运行,工作一段时间,完成工作而不会抛出任何错误/异常,然后在它应该退出时永远挂起。我使用VisualVM(关注正在运行的线程)对其进行了分析,并且还在一个日志语句中进行了打印,以便在应用程序挂起的位置(在main()方法的末尾)进行打印。这是我主要方法的最后一部分:

Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
for(Thread t : threadSet) {
    String daemon = (t.isDaemon()? "Yes" : "No");
    System.out.println("The ${t.getName()} thread is currently running; is it a daemon? ${daemon}.");
}

当我的JAR执行此代码时,我看到以下输出:

The com.google.inject.internal.util.Finalizer thread is currently running; is it a daemon? Yes.
The Signal Dispatcher thread is currently running; is it a daemon? Yes.
The RMI Scheduler(0) thread is currently running; is it a daemon? Yes.
The Attach Listener thread is currently running; is it a daemon? Yes.
The user thread 3 thread is currently running; is it a daemon? No.
The Finalizer thread is currently running; is it a daemon? Yes.
The RMI TCP Accept-0 thread is currently running; is it a daemon? Yes.
The main thread is currently running; is it a daemon? No.
The RMI TCP Connection(1)-10.10.99.8 thread is currently running; is it a daemon? Yes.
The Reference Handler thread is currently running; is it a daemon? Yes.
The JMX server connection timeout 24 thread is currently running; is it a daemon? Yes.

我不会认为我必须担心守护进程(如果我错了,请纠正我),所以将其过滤给非守护进程:

The user thread 3 thread is currently running; is it a daemon? No.
The main thread is currently running; is it a daemon? No.

显然,主线程仍在运行,因为有些东西阻止它退出。嗯,user thread 3看起来很有趣。 VisualVM告诉我们什么?

enter image description here

这是应用程序挂起时的线程视图(上面的控制台输出正在打印时发生了什么)。嗯user thread 3看起来更加可疑!

因此,在杀死应用程序之前,我进行了一次线程转储。以下是user thread 3的堆栈跟踪:

"user thread 3" prio=6 tid=0x000000000dfd4000 nid=0x2360 waiting on condition [0x00000000114ff000]
    java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x0000000782cba410> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
        at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:744)

    Locked ownable synchronizers:
        - None

我以前从来没有分析其中的一个,所以这对我来说是胡言乱语(但也许不是训练有素的眼睛!)。

杀死应用程序后,VisualVM的时间轴每秒都会停止滴答/递增,我可以在时间轴中向后水平滚动到创建user thread 3的位置,并开始将其作为一个唠叨的生活螺纹:

enter image description here

但是我无法弄清楚如何判断代码user thread 3的创建位置。所以我问:

  • 如何判断创建user thread 3的内容,以及(尤其是因为我怀疑是创建该主题的第三方OSS库)它正在创建?
  • 如何对此线程进行分类,诊断和修复?

更新

以下是我的代码在同一时间触发 user thread 3似乎正在创建:

ExecutorService myExecutor = Executors.newCachedThreadPool();
for(Node node : nodes) {
    BootstrapAndKickTask bootAndKickTask = new BootstrapAndKickTask(node, ctx);
    myExecutor.execute(bootAndKickTask);
}

myExecutor.shutdown();
if(!myExecutor.awaitTermination(15, TimeUnit.MINUTES)) {
    TimeoutException toExc = new TimeoutException("Hung after the 15 minute timeout was reached.");
    log.error(toExc);

    throw toExc;
}

此处还有我的GitHub Gist,其中包含完整的主题转储。

3 个答案:

答案 0 :(得分:5)

似乎正在发生什么,但我无法在没有代码的情况下确认,您忘记在ExecutorService上调用shutdown() / shutdownNow()。您正在离开,看起来似乎是一个ThreadPoolExecutor对象可以全局访问,并且在主线程退出时仍然在运行。由于它仍然可以全局访问,因此ExecutorService永远不会调用它的finalize方法,并且永远不会关闭它自己。默认情况下,为ExecutorService创建的线程被创建为非守护进程,并且在需要之后很快就会继续运行。

您应该为我们提供代码供我们查看,或者查看您使用ThreadPoolExecutor的代码,并在使用完毕后正确关闭它。

根据文件:

  

程序中不再引用的池并且没有剩余的池   线程将自动关闭。如果您想确保   即使用户忘记呼叫,也会回收未引用的池   shutdown(),然后你必须安排未使用的线程最终死掉,   通过设置适当的保持活动时间,使用零下限   核心线程和/或设置allowCoreThreadTimeOut(boolean)。

这意味着即使您的程序不再具有对ThreadPoolExecutor的引用,只要池中至少有一个Thread保持活动状态,它就永远不会被回收。您可以查看docs了解相关方法。

答案 1 :(得分:3)

如果您可以粘贴您使用的整个代码,那将是一件好事。 Apache jclouds使用几个执行程序来执行某些任务,您必须关闭它们。

请务必在jclouds close()上的上下文 api 上调用ContextBuilder方法。

答案 2 :(得分:1)

有两个错误:

  1. 您未能获得异常安全的免费分配资源(线程池)
  2. 你会发现你不知道如何处理的错误。
  3. 这是潜在的解决方法。 (我不确定我们是否应该在最后的阻止中包括等待胎面完成)

    ExecutorService myExecutor = Executors.newCachedThreadPool();
    try {
        for(Node node : nodes) {
            BootstrapAndKickTask bootAndKickTask = new BootstrapAndKickTask(node, ctx);
            myExecutor.execute(bootAndKickTask);
        }
    } finally {    
        myExecutor.shutdown();
        if(!myExecutor.awaitTermination(15, TimeUnit.MINUTES)) {
            TimeoutException toExc = new TimeoutException("Hung after the 15 minute timeout was reached.");
            log.error(toExc);
            throw toExc;
        }
    }
    
相关问题