Java:在线程池中为线程设置超时时间

9
我希望为在线程池中执行的线程设置超时时间。目前我有以下代码:

ExecutorService executor = Executors.newFixedThreadPool(8);
for(List<String> l: partition) {            
    Runnable worker = new WorkerThread(l);
    executor.execute(worker);
}       

executor.shutdown();
while (!executor.isTerminated()) {

}

这段代码将一个大的对象列表分割成子列表,并在单个线程内处理这些子列表。但这不是重点。

我想为线程池中的每个单个线程设置超时时间。对于池中的单个线程,我找到了以下解决方案:

Future<?> future = null;

for (List<String> l : partition) {
    Runnable worker = new WorkerThread(l);
    future = executor.submit(worker);
}

try {
    System.out.println("Started..");
    System.out.println(future.get(3, TimeUnit.SECONDS));
    System.out.println("Finished!");
} catch (TimeoutException e) {
    System.out.println("Terminated!");
}

但是这种方法只适用于单个线程。也许我需要将每个线程放入一个List<Future>列表中,遍历此列表并为每个future对象设置超时时间?

有什么建议吗?

使用CountDownLatch后编辑:

CountDownLatch doneSignal = new CountDownLatch(partition.size());
List<Future<?>> tasks = new ArrayList<Future<?>>();
ExecutorService executor = Executors.newFixedThreadPool(8);
for (List<String> l : partition) {
    Runnable worker = new WorkerThread(l);
    tasks.add(executor.submit(doneSignal, worker));
}

doneSignal.await(1, TimeUnit.SECONDS);
if (doneSignal.getCount() > 0) {
    for (Future<?> fut : tasks) {
    if (!fut.isDone()) {
        System.out.println("Task " + fut + " has not finshed!");
        //fut.cancel(true) Maybe we can interrupt a thread this way?!
    }
    }
}

到目前为止,工作得很好。

下一个问题是如何中断一个超时的线程?我尝试使用 fut.cancel(true) 并在工作线程的某些关键循环中添加以下结构:

if(Thread.interrupted()) {
    System.out.println("!!Thread -> " + Thread.currentThread().getName() + " INTERRUPTED!!");
        return;
}

所以,工作线程会在超时后被“杀死”。这是一个好的解决方案吗?

此外:是否可以通过Future接口获取超时的线程名称?目前,我必须在Thread.interrupted()结构的条件语句中打印出名称。

谢谢帮助!

祝好


1
是的,它适用于更多情况,只需将 Futures 放入列表中即可。 - Fildor
4
请不要这样考虑线程。有一些工作正在进行,如果它超时了你希望得到一个超时通知。这与可能正在执行此工作的任何线程无关,而是与工作本身有关。也许两个线程正在共同完成这项工作。也许在那个时间没有任何线程在执行该工作。但是,你想要停止、超时或其他处理的是工作,而不是可能正在工作或不工作的线程。正确地思考线程需要这种微妙的视角转变,这样你才能得出良好的设计。 - David Schwartz
1
@DavidSchwartz 说得好,不过也有可能存在彼此完全独立的情况。我不知道原帖作者是否考虑到了这一点,但看起来是这种情况。 - fge
@fge:这不是关于作品彼此之间的关系,而是关于“工作”(需要完成的任务)的概念与执行它的线程之间的区别。如果工作正在等待某个事情发生,可能没有线程与其相关联。如果可以同时完成工作,则多个线程可以合作完成它。当您在等待某些工作完成时,不应该考虑哪个线程或哪些线程正在处理它。 - David Schwartz
2个回答

3

你看过这个吗?ExecutorService.invokeAll

它应该正是你想要的:调用一组工作线程并在超时时终止它们。

评论后编辑 -(新想法): 您可以使用CountDownLatch等待任务完成,并通过await(long timeout, TimeUnit unit)进行超时! 然后,您甚至可以执行shutdownNow并查看哪些任务花费了太长时间...

编辑2:

  1. 每个Worker完成任务时,使用CountDownLatch计数器进行倒计时。
  2. 在主执行线程中,使用timeout等待该latch。
  3. 当该调用返回时,您可以检查Latches的计数,以查看是否已经超时(如果计数> 0)。
  4. a)count = 0,所有任务及时完成。 b)如果没有,请循环Futures并检查它们的isDone。您不必在ExecutorService上调用shutdown。
  5. 如果您不再需要Executor,则调用shutdown。

注意:在超时和调用其Future的isDone()之间,Workers可能会完成任务。


好的...不过我还是得调用 "executor.shutdown()" 来等待线程是否全部完成,对吧? - sk2212
不一定。你仍然可以遍历未来并轮询isDone()或对每个未来调用get(),或者你可以利用http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html,我认为后者是最好的方式。 - Fildor
啊,好的非常好。 "CountDownLatch" 看起来很不错。你在你的“编辑”行中说,我可以在调用 await 后运行 shutdownNow。那么方法 shutdownNow 会取消所有因设置超时而仍在运行的线程吗?这是我的下一个问题,因为我找不到使用 invokeAll 找出哪些线程“违反”超时的方法。也许用新的方法可以实现? - sk2212
只需要稍作修改。您将在CountDownLatch实例上调用await并设置超时时间。该调用将阻塞,直到超时或闩锁降至0。之后,您可以调用shutdownNow,它会返回一个列表...啊啊啊,刚才读错了,shutdownNow只会返回未启动的任务。但是您仍然可以迭代Futures并询问它们是否完成 :) - Fildor
好的,但是如果我遍历Futures并询问它们是否完成,我必须调用shutdown()或shutdownNow()。如果不调用shutdown()就这样做,我会得到一个“java.util.concurrent.CancellationException”异常:List<Future<String>> list = executor.invokeAll(tasks, 1, TimeUnit.SECONDS); for(Future<String> fut : list){ if(fut.isDone()) { System.out.println("Task " + fut.get() + " 已经完成."); } } Future返回一个带有线程名称的字符串。编辑:不使用“CountDownLatch”。 - sk2212
我使用CountDownLatch编辑我的帖子,目前运行良好。只有一些其他的问题;-)。 - sk2212

0
Future future = executorService.submit(callable)
future.get(timeout, unit)

更多信息请参见此链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接