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

标签 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

这是应用程序挂起时的线程 View (打印上面的控制台输出时发生的情况)。嗯,用户线程 3 看起来更加可疑!

因此,在终止该应用程序之前,我进行了线程转储。这是用户线程 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。所以我问:

  • 我如何知道是什么在创建用户线程 3,以及它在哪里(特别是因为我怀疑它是一个正在创建线程的第 3 方 OSS 库)创建?
  • 如何分类、诊断和修复此线程挂起?

更新:

这是我的代码,大约在同一时间 用户线程 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其中包含完整的线程转储。

最佳答案

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

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

根据文档:

A pool that is no longer referenced in a program AND has no remaining threads will be shutdown automatically. If you would like to ensure that unreferenced pools are reclaimed even if users forget to call shutdown(), then you must arrange that unused threads eventually die, by setting appropriate keep-alive times, using a lower bound of zero core threads and/or setting allowCoreThreadTimeOut(boolean).

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

关于Java:如何修复挂起的线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25560878/

相关文章:

c - 原子整数增量

java - 通过多个线程删除 java.util.Collection 元素

java - 无法使用maven构建项目

c - 为什么使用 usleep 而不是 sleep

java - 如果满足条件,则以原子方式写入两个变量

c# - 栈空异常

java - ConcurrentHashMap 中的本地引用

java - 如何使用 Java 的 3D 库?

java - 在 `value` 标签内执行表达式

go - 与 Channels 相比,sync.WaitGroup 的优势是什么?