ExecutorService,如何等待所有任务完成


196

等待所有任务ExecutorService完成的最简单方法是什么?我的任务主要是计算,因此我只想运行大量的作业-每个内核上一个。现在,我的设置如下所示:

ExecutorService es = Executors.newFixedThreadPool(2);
for (DataTable singleTable : uniquePhrases) {   
    es.execute(new ComputeDTask(singleTable));
}
try{
    es.wait();
} 
catch (InterruptedException e){
    e.printStackTrace();
}

ComputeDTask实现可运行。这似乎是正确执行的任务,但代码崩溃上wait()IllegalMonitorStateException。这很奇怪,因为我玩了一些玩具示例,而且看起来很奏效。

uniquePhrases包含数以万计的元素。我应该使用其他方法吗?我正在寻找尽可能简单的东西


1
如果您想使用等待(答案告诉您不要):es当您要等待时,始终需要在对象上进行同步(在这种情况下)-等待时锁将自动释放
mihi

13
初始化ThreadPool的更好方法是Executors.newFixedThreadPool(System.getRuntime().availableProcessors());

7
Runtime.getRuntime()。availableProcessors();
维加莫夫(Vik Gamov)

[本] [1]是一个有趣的选择。[1]:stackoverflow.com/questions/1250643/...
的RăzvanPetruescu

1
如果您想使用CountDownLatch,请使用以下示例代码:stackoverflow.com/a/44127101/4069305
Tuan Pham

Answers:


213

最简单的方法是使用ExecutorService.invokeAll()单行代码执行所需的操作。用您的话来说,您将需要修改或包装ComputeDTask以实现Callable<>,这可以为您带来更大的灵活性。可能在您的应用程序中有一个有意义的实现Callable.call(),但是如果不使用,则可以通过以下方式包装它Executors.callable()

ExecutorService es = Executors.newFixedThreadPool(2);
List<Callable<Object>> todo = new ArrayList<Callable<Object>>(singleTable.size());

for (DataTable singleTable: uniquePhrases) { 
    todo.add(Executors.callable(new ComputeDTask(singleTable))); 
}

List<Future<Object>> answers = es.invokeAll(todo);

正如其他人指出的那样,您可以使用的超时版本(invokeAll()如果适用)。在此示例中,answers将包含一堆Futures,这些s将返回空值(请参阅。的定义Executors.callable()。可能要做的是稍微重构,以便您可以返回有用的答案或对基础的引用ComputeDTask,但是我可以从你的例子中看不出来。

如果不清楚,请注意,invokeAll()直到完成所有任务后该操作才会返回。(即,如果询问,集合中的所有Futures answers都会报告.isDone()。)这避免了所有手动关闭,awaitTermination等,并允许您重用此方法ExecutorService根据需要在多个循环中整洁地。

关于SO有一些相关问题:

这些都不是您提出问题的严格要点,但是它们确实为人们的想法Executor/ ExecutorService应该如何使用提供了一些色彩。


9
如果您要批量添加所有作业并且挂在Callables列表上,那么这是完美的选择,但是如果您在回调或事件循环情况下调用ExecutorService.submit(),则此方法将无效。
Desty

2
我认为值得一提的是,当不再需要ExecutorService时,仍应调用shutdown(),否则线程将永远不会终止(除非corePoolSize = 0或allowCoreThreadTimeOut = true的情况)。
约翰29年

惊人!正是我想要的。非常感谢您分享答案。让我尝试一下。
MohamedSanaulla

59

如果要等待所有任务完成,请使用shutdown方法代替wait。然后按awaitTermination

另外,您可以Runtime.availableProcessors用来获取硬件线程的数量,以便可以正确地初始化线程池。


27
shutdown()阻止ExecutorService接受新任务并关闭空闲的工作线程。未指定等待关闭完成,并且ThreadPoolExecutor中的实现不等待。
阿兰·奥德

1
@Alain-谢谢。我应该提到awaitTermination。固定。
NG。

5
如果要完成一项任务必须安排其他任务怎么办?例如,您可以进行多线程树遍历,将分支移交给工作线程。在这种情况下,由于ExecutorService立即关闭,因此无法接受任何递归调度的作业。
布莱恩·戈登

2
awaitTermination需要超时时间作为参数。虽然可以提供有限的时间并在其周围放置一个循环以等待所有线程完成,但我想知道是否有更优雅的解决方案。
Abhishek S

1
你是对的,但看到这个答案- stackoverflow.com/a/1250655/263895 -你总是可以给它一个令人难以置信的长超时
NG。

48

如果等待任务中的所有任务ExecutorService并非完全是您的目标,而是等待直到特定一批任务完成,您可以使用CompletionService,特别是ExecutorCompletionService

这个想法是创建一个ExecutorCompletionService包装Executor,通过提交一些已知数量的任务CompletionService,然后使用(哪个块)或从完成队列中提取相同数量的结果。take()poll()(哪个不)。绘制与提交的任务相对应的所有预期结果后,您就知道它们已经完成。

让我再说一次,因为它在界面中并不明显:您必须知道要放入多少东西CompletionService才能知道要抽出多少东西。这对take()方法尤其重要:一次调用太多,它将阻塞您的调用线程,直到其他线程向同一线程提交另一个作业为止CompletionService

在《Java Concurrency in Practice》一书中有一些示例说明了如何使用CompletionService


这是对我的答案的一个很好的反驳-我想说,问题的直接答案是invokeAll();。但是@seh在向ES提交工作组并等待它们完成时是正确的... --JA
andersoj,2010年

@ om-nom-nom,感谢您更新链接。我很高兴看到答案仍然有用。
2013年

1
好答案,我不知道CompletionService
Vic 2015年

1
如果您不想关闭现有的ExecutorService,而只想提交一批任务,并知道它们何时完成,则可以使用这种方法。
ToolmakerSteve

11

如果要等待执行程序服务完成执行,请调用shutdown(),然后调用awaitTermination(units,unitType),例如awaitTermination(1, MINUTE)。ExecutorService不会在自己的监视器上进行阻止,因此您不能使用waitetc。


我认为这是awaitTermination。
NG。

@SB-谢谢-我的记忆很容易出错!我已经更新了名称并添加了链接以确保。
mdma

要“永远”等待,请像awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); stackoverflow.com/a/1250655/32453
rogerdpack 2014年

我认为这是最简单的方法
Shervin Asgari

1
@MosheElisha,确定吗?docs.oracle.com/javase/8/docs/api/java/util/concurrent/…表示启动有序关闭,在该关闭中将执行先前提交的任务,但不会接受任何新任务。
Jaime Hablutzel

7

您可以等待作业在一定间隔内完成:

int maxSecondsPerComputeDTask = 20;
try {
    while (!es.awaitTermination(uniquePhrases.size() * maxSecondsPerComputeDTask, TimeUnit.SECONDS)) {
        // consider giving up with a 'break' statement under certain conditions
    }
} catch (InterruptedException e) {
    throw new RuntimeException(e);    
}

或者您可以使用ExecutorService提交Runnable)并收集返回的Future对象,然后依次对每个对象调用get()以等待它们完成。

ExecutorService es = Executors.newFixedThreadPool(2);
Collection<Future<?>> futures = new LinkedList<<Future<?>>();
for (DataTable singleTable : uniquePhrases) {
    futures.add(es.submit(new ComputeDTask(singleTable)));
}
for (Future<?> future : futures) {
   try {
       future.get();
   } catch (InterruptedException e) {
       throw new RuntimeException(e);
   } catch (ExecutionException e) {
       throw new RuntimeException(e);
   }
}

正确处理InterruptedException非常重要。它使您或您的图书馆用户安全地终止了一个漫长的过程。


6

只需使用

latch = new CountDownLatch(noThreads)

在每个线程中

latch.countDown();

并作为障碍

latch.await();

6

IllegalMonitorStateException的根本原因:

抛出该异常指示线程试图在对象的监视器上等待,或者通知其他线程在对象监视器上等待而没有拥有指定的监视器。

从您的代码中,您仅在ExecutorService上调用了wait()而不拥有锁。

下面的代码将修复 IllegalMonitorStateException

try 
{
    synchronized(es){
        es.wait(); // Add some condition before you call wait()
    }
} 

请按照以下方法之一等待已提交给的所有任务的完成ExecutorService

  1. Future从头submit开始遍历所有任务,ExecutorService并通过阻止对象调用get()来检查状态Future

  2. 在上使用invokeAllExecutorService

  3. 使用CountDownLatch

  4. 使用ForkJoinPoolnewWorkStealingPoolExecutors(由于Java 8)

  5. 按照oracle文档页面中的建议关闭池

    void shutdownAndAwaitTermination(ExecutorService pool) {
       pool.shutdown(); // Disable new tasks from being submitted
       try {
       // Wait a while for existing tasks to terminate
       if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
           pool.shutdownNow(); // Cancel currently executing tasks
           // Wait a while for tasks to respond to being cancelled
           if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
       }
    } catch (InterruptedException ie) {
         // (Re-)Cancel if current thread also interrupted
         pool.shutdownNow();
         // Preserve interrupt status
         Thread.currentThread().interrupt();
    }

    如果要在使用选项5(而不是选项1至4)时优雅地等待所有任务完成,请更改

    if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {

    一个while(condition)这对于每1分钟的检查。


6

您可以使用ExecutorService.invokeAll方法,它将执行所有任务并等待所有线程完成其任务。

这是完整的javadoc

您还可以使用此方法的用户重载版本来指定超时。

这是带有的示例代码 ExecutorService.invokeAll

public class Test {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        List<Callable<String>> taskList = new ArrayList<>();
        taskList.add(new Task1());
        taskList.add(new Task2());
        List<Future<String>> results = service.invokeAll(taskList);
        for (Future<String> f : results) {
            System.out.println(f.get());
        }
    }

}

class Task1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(2000);
            return "Task 1 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task1";
        }
    }
}

class Task2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(3000);
            return "Task 2 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task2";
        }
    }
}

3

我也有我要爬网的一组文档的情况。我从应该处理的初始“种子”文档开始,该文档包含到也应该处理的其他文档的链接,依此类推。

在我的主程序中,我只想编写类似以下内容的代码,其中Crawler控制着多个线程。

Crawler c = new Crawler();
c.schedule(seedDocument); 
c.waitUntilCompletion()

如果我想导航一棵树,也会发生同样的情况。我将弹出根节点,每个节点的处理器将在必要时将子级添加到队列中,一堆线程将处理树中的所有节点,直到没有更多节点为止。

我在JVM中找不到任何东西,我认为这有点令人惊讶。因此,我编写了一个类ThreadPool,可以直接使用它或使用子类来添加适合该域的方法,例如schedule(Document)。希望能帮助到你!

线程池Javadoc | 马文


Doc Link死了
Manti_Core

@Manti_Core-谢谢,更新。
阿德里安·史密斯

2

添加集合中的所有线程,然后使用提交invokeAll。如果可以使用invokeAllof的方法ExecutorService,则在所有线程完成之前,JVM不会进入下一行。

这里有个很好的例子: 通过ExecutorService调用invokeAll


1

将您的任务提交到Runner中,然后等待调用方法waitTillDone(),如下所示:

Runner runner = Runner.runner(2);

for (DataTable singleTable : uniquePhrases) {

    runner.run(new ComputeDTask(singleTable));
}

// blocks until all tasks are finished (or failed)
runner.waitTillDone();

runner.shutdown();

要使用它,请添加此gradle / maven依赖项: 'com.github.matejtymes:javafixes:1.0'

有关更多详细信息,请参见:https : //github.com/MatejTymes/JavaFixes或此处:http : //matejtymes.blogspot.com/2016/04/executor-that-notify-you-when-task.html



0

我将等待执行程序终止,并指定一个您认为适合完成任务的超时时间。

 try {  
         //do stuff here 
         exe.execute(thread);
    } finally {
        exe.shutdown();
    }
    boolean result = exe.awaitTermination(4, TimeUnit.HOURS);
    if (!result)

    {
        LOGGER.error("It took more than 4 hour for the executor to stop, this shouldn't be the normal behaviour.");
    }

0

听起来像您需要ForkJoinPool并使用全局池执行任务。

public static void main(String[] args) {
    // the default `commonPool` should be sufficient for many cases.
    ForkJoinPool pool = ForkJoinPool.commonPool(); 
    // The root of your task that may spawn other tasks. 
    // Make sure it submits the additional tasks to the same executor that it is in.
    Runnable rootTask = new YourTask(pool); 
    pool.execute(rootTask);
    pool.awaitQuiescence(...);
    // that's it.
}

美的pool.awaitQuiescence地方在于该方法将阻止利用调用者的线程来执行其任务,然后在该方法真正为空时返回。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.