程序挂起,因为线程不会终止

时间:2015-03-13 13:29:12

标签: java multithreading

我有这种奇怪的行为。我的程序挂起了,尽管我正在设置强制终止它的时间。任何人都可以指出这种奇怪的行为可能是什么?

这是我启动线程的代码

protected void pendingTaskStarter() throws Exception {
    ExecutorService service = Executors.newFixedThreadPool(maxThreadNum);
    ArrayList<Future<Runnable>> futures = new ArrayList<Future<Runnable>>();

    System.out.println("max thread num: " + maxThreadNum);
    for(int i=0;i<maxThreadNum;i++){

        Thread t = new PendingTaskConsumer();
        Future<?> f=service.submit(t);
        futures.add((Future<Runnable>) f);  
    }

    for (Future<?> future:futures) {
        future.get(5l,TimeUnit.MINUTES); // maximum thread's life is 5min (5l <-- 5 in long )
    }   
    service.shutdownNow();

}

我100%确定我的程序根据PendingTaskConsumer类中的输出在PendingTaskConsumer类中的某个位置挂起。

无论如何,PendingTaskConsumer中的代码应该是无关紧要的,因为线程应该被强制终止。 我的问题是下面一行中没有按预期做的那些情节。

future.get(5l,TimeUnit.MINUTES);

该程序正在Linux(Ubuntu)并使用openjdk-7-jdk backage ( version 1.7)

运行

2 个答案:

答案 0 :(得分:1)

嗯......似乎缺少适当的异常处理。

/**  
 * Waits if necessary for at most the given time for the computation  
 * to complete, and then retrieves its result, if available.  
 *  
 * @param timeout the maximum time to wait  
 * @param unit the time unit of the timeout argument  
 * @return the computed result  
 * @throws CancellationException if the computation was cancelled  
 * @throws ExecutionException if the computation threw an
 * exception  
 * @throws InterruptedException if the current thread was interrupted
 * while waiting  
 * @throws **TimeoutException if the wait timed out**  
 */  
V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException;

如果您对future.get(5l,TimeUnit.MINUTES);的来电过期,则会抛出Exception而您的service永远不会关闭。

所以(可以假设)其内部线程仍在运行,至少是执行长时间运行任务的线程。看到这些是非守护进程线程(您必须自定义执行程序的ThreadFactory),这些挂起的线程会阻止JVM关闭。

如果您希望强行终止任务,您可以:

  1. 设计您的可运行任务以响应Thread.isInterrupted(),并在它看到变为真时退出。 (shutdownNow的实际效果是设置executor的线程的中断标志)
  2. 如果你不能(因为你的某些图书馆的方法没有听到中断),那么拥有一个dameon线程似乎是唯一的选择。
  3. 但无论如何,我要先将executor关闭,或者放在finally条款中。

答案 1 :(得分:1)

正如@GPI在他/她的回答中已经说明的那样,如果您的任务没有通过Thread.interrupt()来回应线程中断,则取消将无法正常工作。请参阅Oracle: The Java™ Tutorials - Concurrency - InterruptsJava Executors: how can I stop submitted tasks?

但是即使你的任务没有在线程被中断时立即停止(=设置了中断标志),你也可以超时你的方法。不要通过调用get()的每个Future方法来听取任务完成情况!这并不是必要的,因为你对他们的结果并不感兴趣。只需关闭ExecutorService并等待其终止。

protected void pendingTaskStarter() throws InterruptedException {
    ExecutorService service = Executors.newFixedThreadPool(maxThreadNum);

    for (int i = 0; i < maxThreadNum; i++) {
        service.submit(new PendingTaskConsumer());
    }

    // Shutdown service.
    // This will continue to process already submitted tasks.
    service.shutdown();

    try {
        // wait at least 5 minutes for the tasks to complete
        if (!service.awaitTermination(5, TimeUnit.MINUTES)) {
            // still not done yet -> force shutdown
            // this will interrupt currently running tasks.
            service.shutdownNow();
        }
    } catch (InterruptedException ex) {
        // someone interrupted this thread. Shutdown tasks immediately
        service.shutdownNow();
        // propagate exception
        throw ex;
    }
}

那么这里会发生什么?

  • 首先,提交所有任务。
  • 然后在shutdown()上调用ExecutorService。请注意,该服务将continue to execute already submitted tasks, but it will not accept new tasks
  • 现在,我们等待服务终止,方法是调用awaitTermination,超时时间为5分钟。如果此方法返回true,这意味着服务已在时间内终止,一切正常,我们就完成了。但如果它在5分钟后返回false,则意味着该服务仍在运行,因为有些任务需要更多时间才能完成。
  • 在这种情况下,我们通过致电shutdownNow()立即关闭服务。请注意,此方法不会阻止!它只是尝试取消所有正在运行的任务,通常是通过中断当前正在执行它们的线程。 这正是您的任务应该正确响应线程中断的原因!如果他们不这样做,中断线程将完全没有效果!
  • 无论shutdownNow是否成功 - 我们已经完成,不要再等了。因此,可能仍然存在一些正在运行的任务,因为它们不会在线程中断时做出反应(通常是因为任务要么没有正确实现,要么是故意无意中执行)。但我们根本不在乎。
  • 请注意异常处理程序:当另一个线程在方法awaitTermination中等待时调用我们方法的线程时,会抛出InterruptedException。我们通过关闭服务并重新抛出异常来处理它!这是一件好事,使我们的方法成为一个可以自行取消的长期运行操作!