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

Dir*_*oys 4 java concurrency multithreading 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}.");
}
Run Code Online (Sandbox Code Playgroud)

当我的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.
Run Code Online (Sandbox Code Playgroud)

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

The user thread 3 thread is currently running; is it a daemon? No.
The main thread is currently running; is it a daemon? No.
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

这是应用程序挂起的点的线程视图(上面的控制台输出打印时发生的事情).嗯user thread 3,看起来更加可疑!

所以在杀死应用程序之前我采取了一个线程转储.这是stacktrace 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
Run Code Online (Sandbox Code Playgroud)

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

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

在此输入图像描述

但是我无法弄清楚如何分辨代码user thread 3的创建方式.所以我问:

  • 我怎么知道创建什么user thread 3,以及在哪里(特别是因为我怀疑它的第三方OSS lib正在创建线程)它正在被创建?
  • 我如何分类,诊断和修复此线程挂起?

更新:

这里是我的代码被解雇大约在同一时间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;
}
Run Code Online (Sandbox Code Playgroud)

这里还有我的GitHub Gist,其中包含完整的线程转储.

Smi*_*_61 5

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

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

根据文件:

程序中不再引用且没有剩余线程的池将自动关闭.如果您希望确保即使用户忘记调用shutdown()也会回收未引用的池,那么您必须通过设置适当的保持活动时间,使用零核心线程的下限和/或来安排未使用的线程最终死亡设置allowCoreThreadTimeOut(boolean).

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