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

时间:2010-07-16 23:12:55

标签: java multithreading threadpool executorservice

等待ExecutorService的所有任务完成的最简单方法是什么?我的任务主要是计算,所以我只想运行大量的工作 - 每个核心一个。现在我的设置看起来像这样:

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

ComputeDTask实现了runnable。这似乎可以正确执行任务,但代码在wait()上与IllegalMonitorStateException崩溃。这很奇怪,因为我玩了一些玩具示例,它似乎工作。

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

15 个答案:

答案 0 :(得分:196)

最简单的方法是使用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将包含一堆Future,它们将返回空值(参见Executors.callable()的定义。可能你想做的是轻微的重构,这样你就可以得到一个有用的答案,或对基础ComputeDTask的引用,但我无法从你的例子中说出来。

如果不清楚,请注意invokeAll()在完成所有任务之前不会返回。 (即,如果被问及,Future集合中的所有answers将报告.isDone()。)这可以避免所有手动关闭,等待终止等...并允许您重复使用此{{ {1}}如果需要,可以整齐地进行多个周期。

有关SO的一些相关问题:

这些都不是严格意义上的问题,但它们确实提供了一些关于人们应该如何使用ExecutorService / Executor的颜色。

答案 1 :(得分:53)

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

此外,您可以使用Runtime.availableProcessors获取硬件线程数,以便正确初始化线程池。

答案 2 :(得分:47)

如果等待ExecutorService中的所有任务完成并不是您的目标,而是等到特定批量任务完成后,您可以使用{{3 - 具体来说,是CompletionService

我们的想法是通过ExecutorCompletionService创建ExecutorCompletionServiceExecutorCompletionService某些已知数量任务包裹起来,然后绘制来自完成队列相同数量的结果使用submit(阻止)或take()(不是)。一旦你绘制了与你提交的任务相对应的所有预期结果,你就知道它们已经完成了。

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

poll()中有some examples showing how to use CompletionService

答案 3 :(得分:9)

如果您想等待执行程序服务完成执行,请致电shutdown()然后awaitTermination(units, unitType),例如awaitTermination(1, MINUTE)。 ExecutorService不会阻止它自己的监视器,因此你不能使用wait等。

答案 4 :(得分:6)

您可以等待某段时间内完成作业:

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 对于正确处理非常重要。它可以让您或您的库的用户安全地终止长时间的过程。

答案 5 :(得分:5)

只需使用

latch = new CountDownLatch(noThreads)

在每个帖子中

latch.countDown();

并作为障碍

latch.await();

答案 6 :(得分:5)

IllegalMonitorStateException的根本原因:

  

抛出此异常表示某个线程试图在对象的监视器上等待,或者在没有指定监视器的情况下通知在对象监视器上等待的其他线程。

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

以下代码将修复IllegalMonitorStateException

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

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

  1. Future上对submit的所有ExecutorService个任务进行迭代,并在get()对象上使用阻止调用Future检查状态

  2. ExecutorService

  3. 上使用invokeAll
  4. 使用CountDownLatch

  5. 使用Executorsvoid 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(); } if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { (自java 8开始)

  6. 按照oracle文档ForkJoinPool

    中的建议关闭池
    while(condition)

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

    Android Studio 2.1 Preview 5
    Build #AI-143.2730271, built on March 31, 2016
    JRE: 1.7.0_80-b15 amd64
    JVM: Java HotSpot(TM) 64-Bit Server VM by Oracle Corporation
    

    class DV: UIView { var lines: [Line] = [] var firstPoint: CGPoint! var lastPoint: CGPoint! required init?(coder aDecoder: NSCoder){ super.init(coder: aDecoder)! } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { lastPoint = touches.first!.locationInView(self) } override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) { var newPoint = touches.first!.locationInView(self) lines.append(Line(start: lastPoint, end: newPoint)) lastPoint = newPoint self.setNeedsDisplay() } override func drawRect(rect: CGRect) { var context = UIGraphicsGetCurrentContext() CGContextBeginPath(context) // print("fine") starts at beginning only for line in lines { CGContextMoveToPoint(context,line.start.x , line.start.y) CGContextAddLineToPoint(context, line.end.x, line.end.y) } CGContextSetRGBFillColor(context, 0, 0, 0, 1) CGContextSetLineCap(context, .Round) CGContextSetLineWidth(context, 5) CGContextStrokePath(context) } } ,每1分钟检查一次。

答案 7 :(得分:4)

您可以使用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";
        }
    }
}

答案 8 :(得分:3)

我还有一种情况是我有一组要抓取的文件。我从一个应该处理的初始“种子”文档开始,该文档包含应该处理的其他文档的链接,等等。

在我的主程序中,我只想编写如下内容,Crawler控制一堆线程。

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

如果我想导航一棵树,也会发生同样的情况;我会弹出根节点,每个节点的处理器会根据需要将子节点添加到队列中,并且一堆线程将处理树中的所有节点,直到不再存在。

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

AutoStopThreadPool Javadoc | Download

答案 9 :(得分:2)

添加集合中的所有主题并使用invokeAll提交。 如果您可以使用invokeAll ExecutorService方法,那么在所有线程完成之前,JVM将不会继续下一行。

这里有一个很好的例子: invokeAll via ExecutorService

答案 10 :(得分:1)

将您的任务提交到亚军,然后等待调用方法 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-notifies-you-when-task.html

答案 11 :(得分:0)

一个简单的替代方法是使用线程和连接。 请参阅:Joining Threads

答案 12 :(得分: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.");
    }

答案 13 :(得分: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,其中方法将阻止利用调用者的线程执行其任务,然后在真正为空时返回。

答案 14 :(得分:0)

怎么样?

Object lock = new Object();
CountDownLatch cdl = new CountDownLatch(threadNum);
for (int i = 0; i < threadNum; i++) {
    executorService.execute(() -> {

        synchronized (lock) {
            cdl.countDown();
            try {
                lock.wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    });
}
cdl.await();
synchronized (lock) {
    lock.notifyAll();
}

如果您不将新任务添加到ExecutorService,则可能会等待所有当前任务完成