如何使用ExecutorService等待所有线程完成?


381

我需要一次执行一些任务4,如下所示:

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
    taskExecutor.execute(new MyTask());
}
//...wait for completion somehow

完成所有步骤后如何通知我?现在,我想不出什么比设置一些全局任务计数器并在每个任务结束时减少它,然后无限循环监视此计数器为0更好的了。或获取期货的列表,并在无限循环中对所有期货进行isDone监视。什么是不涉及无限循环的更好的解决方案?

谢谢。

Answers:


446

基本上在ExecutorService您致电shutdown()之后awaitTermination()

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
  taskExecutor.execute(new MyTask());
}
taskExecutor.shutdown();
try {
  taskExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
  ...
}

9
这恰好是shutdown / awaitTermination的意思
matt b

31
如果此任务处理是一次性事件,则这是一个很好的模式。但是,如果在同一运行时期间重复执行此操作,则不是最佳选择,因为您将在每次执行时重复创建和删除线程。
sjlee

44
我正在寻找Long.MAX_VALUE, TimeUnit.NANOSECONDS等同于没有超时的任何正式文档。
山姆·哈威尔

15
我无法相信您必须使用shutdown才能加入所有当前线程(使用shutdown之后,您将无法再使用执行程序)。建议改用Future的清单...
rogerdpack 2012年

20
@SamHarwell请参阅以下部分的java.util.concurrent软件包说明文件Timing要“永远”等待,可以使用Long.MAX_VALUE
beluchin

174

使用CountDownLatch

CountDownLatch latch = new CountDownLatch(totalNumberOfTasks);
ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
  taskExecutor.execute(new MyTask());
}

try {
  latch.await();
} catch (InterruptedException E) {
   // handle
}

并在您的任务中(包含在try / finally中)

latch.countDown();

3
没有4个任务。一次完成4个“一定数量的任务”。
cletus

2
抱歉,我误解了这个问题。是的,任务数量应该是CountDownLatch构造函数的参数
ChssPly76

3
我发现此解决方案比其他解决方案更优雅,看起来是为此目的而设计的,并且简单明了。
09年

3
如果在开始之前不知道任务数量怎么办?
cletus

11
@cletus-那么您就不用CountDownLatch了:-)请注意,我并不是在说这种方法比您的方法更好。但是,我发现在现实生活中,我确实知道任务数量,每个部署确实需要配置线程池设置,并且池可以重用。因此,通常我会使用Spring注入的线程池,并将它们设置为原型,然后仅为了等待线程完成而手动将其关闭,这 似乎并不理想。
2009年

82

ExecutorService.invokeAll() 为你做。

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
List<Callable<?>> tasks; // your tasks
// invokeAll() returns when all tasks are complete
List<Future<?>> futures = taskExecutor.invokeAll(tasks);

如果/当您一次启动“ 4”个线程时,困难就来了,然后逐个连接,然后加入/让所有4个线程完成...
rogerdpack 2012年

@rogerdpack:我仍在学习执行程序和其他东西。但是响应您的要求。一次4个线程不应该属于使用上述答案执行的批处理任务吗?
Mukul Goel 2014年

3
仅当您事先知道任务数时,此方法才有效。
康斯坦丁2015年

3
我认为,当futures返回时,任务尚未完成。它们可能会在将来完成,您将获得指向结果的链接。这就是为什么它被称为的原因Future。您具有Future.get()方法,它将等待任务完成以获取结果。
AlikElzin-kilaka

5
JavaDocs中的@ AlikElzin-kilaka引用(在答案中链接):“执行给定的任务,并在完成所有操作后返回保存其状态和结果的Future列表。Future.isDone()对于返回列表的每个元素都是true。 ”
绿巨人

47

您还可以使用期货列表:

List<Future> futures = new ArrayList<Future>();
// now add to it:
futures.add(executorInstance.submit(new Callable<Void>() {
  public Void call() throws IOException {
     // do something
    return null;
  }
}));

然后,当您要在所有线程上进行连接时,从本质上讲,它们等效于在每个线程上进行连接(它具有从子线程到主线程重新引发异常的附加好处):

for(Future f: this.futures) { f.get(); }

基本上,诀窍是一次在每个Future上调用.get(),而不是无限循环地(全部或每个)调用isDone()。因此,确保您在最后一个线程结束后立即“继续”通过该块。需要注意的是,由于.get()调用会重新引发异常,因此,如果其中一个线程死亡,则可能在其他线程完成操作之前就从此线程举起[为避免这种情况,您可以catch ExecutionException在get调用周围添加一个]。另一个警告是,它保留了对所有线程的引用,因此,如果它们具有线程局部变量,则直到经过该块后它们才会被收集(尽管如果出现问题,您可以通过删除来解决这个问题)未来的ArrayList)。如果您想知道哪个未来“最先完成”https://stackoverflow.com/a/31885029/32453


3
要知道哪个“先加工”,请使用ExecutorCompletionService.takestackoverflow.com/a/11872604/199364
ToolmakerSteve

34

在Java8中,您可以使用CompletableFuture来做到这一点:

ExecutorService es = Executors.newFixedThreadPool(4);
List<Runnable> tasks = getTasks();
CompletableFuture<?>[] futures = tasks.stream()
                               .map(task -> CompletableFuture.runAsync(task, es))
                               .toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();    
es.shutdown();

3
这是一个非常优雅的解决方案。
mattvonb

ExecutorService es = Executors.newFixedThreadPool(4); List< Future<?>> futures = new ArrayList<>(); for(Runnable task : taskList) { futures.add(es.submit(task)); } for(Future<?> future : futures) { try { future.get(); }catch(Exception e){ // do logging and nothing else } }
user2862544

es.shutdown()之后是否需要@AdamSkywalker awaitTermination()?
gaurav '19

@gaurav调用shutdown时,某些任务可能尚未完成。因此,awaitTermination将阻塞调用线程,直到一切完成。这取决于您是否需要在该线程中等待结果。
AdamSkywalker

@AdamSkywalker很好的答案。如果我不需要等待结果,则不要调用awaitTermination()是有意义的。
gaurav '19

26

只是我的两分钱。为了克服CountDownLatch事先知道任务数量的要求,您可以通过使用简单的简单方法来完成Semaphore

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
int numberOfTasks=0;
Semaphore s=new Semaphore(0);
while(...) {
    taskExecutor.execute(new MyTask());
    numberOfTasks++;
}

try {
    s.aquire(numberOfTasks);
...

在你的任务只是打电话s.release(),你会latch.countDown();


看到这一点,我首先想知道如果在release调用之前发生一些调用是否会出现问题acquire,但是在阅读了Semaphore文档之后,我认为还可以。
ToolmakerSteve

13

游戏有点晚了,但是为了完成...

您可以按照好莱坞的原则,而不是“等待”完成所有任务,“完成后,不要叫我,我叫您”。我认为生成的代码更加优雅...

番石榴提供了一些有趣的工具来完成此任务。

一个例子 ::

将ExecutorService包装到ListeningExecutorService ::中

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

提交可调用的集合以执行::

for (Callable<Integer> callable : callables) {
  ListenableFuture<Integer> lf = service.submit(callable);
  // listenableFutures is a collection
  listenableFutures.add(lf)
});

现在最重要的部分:

ListenableFuture<List<Integer>> lf = Futures.successfulAsList(listenableFutures);

将回调附加到ListenableFuture,可以在所有期货完成时通知您::

        Futures.addCallback(lf, new FutureCallback<List<Integer>>() {
        @Override
        public void onSuccess(List<Integer> result) {
            log.info("@@ finished processing {} elements", Iterables.size(result));
            // do something with all the results
        }

        @Override
        public void onFailure(Throwable t) {
            log.info("@@ failed because of :: {}", t);
        }
    });

这还提供了一个优势,即一旦处理完成,您就可以将所有结果收集在一个地方...

更多信息在这里


2
很干净。即使在Android上也可以正常工作。只是不得不使用runOnUiThread()onSuccess()
DSchmidt'2

12

Java 5和更高版本中的CyclicBarrier类是为这种事情而设计的。


7
太酷了,永远不会记住此数据结构的名称。但是,仅当您事先知道要排队的任务数量时才适用。
2012年

是的,您认为您可以使用当前线程以及所有子线程来突破障碍,那么当您通过它时,您会知道子线程已经完成...
rogerdpack 2012年

其实这是错误的答案。CyclicBarrier专为部分设计。CountDownLatch设计用于等待事件
gstackoverflow


6

这是两个选择,请稍稍混淆哪个是最好的选择。

选项1:

ExecutorService es = Executors.newFixedThreadPool(4);
List<Runnable> tasks = getTasks();
CompletableFuture<?>[] futures = tasks.stream()
                               .map(task -> CompletableFuture.runAsync(task, es))
                               .toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();    
es.shutdown();

选项2:

ExecutorService es = Executors.newFixedThreadPool(4);
List< Future<?>> futures = new ArrayList<>();
for(Runnable task : taskList) {
    futures.add(es.submit(task));
}

for(Future<?> future : futures) {
    try {
        future.get();
    }catch(Exception e){
        // do logging and nothing else
    }
}
es.shutdown();

这里放future.get(); 在尝试捕获中是个好主意吧?


5

您可以将任务包装在另一个可运行的程序中,该程序将发送通知:

taskExecutor.execute(new Runnable() {
  public void run() {
    taskStartedNotification();
    new MyTask().run();
    taskFinishedNotification();
  }
});

1
花了我一段时间看看这将如何解决OP的问题。首先,请注意,此包装是每个任务的包装,而不是启动所有任务的代码的包装。大概,每次开始都会增加一个计数器,每次完成都会减少该计数器,或者增加一个completed计数器。因此,在全部启动之后,在每次通知时都可以确定所有任务是否已完成。请注意,使用至关重要,即使任务失败,也要给出try/finally完成的通知(或catch块中的替代通知)。否则,将永远等待。
ToolmakerSteve

3

我刚刚编写了一个示例程序来解决您的问题。没有给出简明的实现,因此我将添加一个。尽管可以使用executor.shutdown()and executor.awaitTermination(),但这不是最佳实践,因为不同线程所花费的时间是不可预测的。

ExecutorService es = Executors.newCachedThreadPool();
    List<Callable<Integer>> tasks = new ArrayList<>();

    for (int j = 1; j <= 10; j++) {
        tasks.add(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                int sum = 0;
                System.out.println("Starting Thread "
                        + Thread.currentThread().getId());

                for (int i = 0; i < 1000000; i++) {
                    sum += i;
                }

                System.out.println("Stopping Thread "
                        + Thread.currentThread().getId());
                return sum;
            }

        });
    }

    try {
        List<Future<Integer>> futures = es.invokeAll(tasks);
        int flag = 0;

        for (Future<Integer> f : futures) {
            Integer res = f.get();
            System.out.println("Sum: " + res);
            if (!f.isDone()) 
                flag = 1;
        }

        if (flag == 0)
            System.out.println("SUCCESS");
        else
            System.out.println("FAILED");

    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }

很好地展示了对future.get的使用-了解这一点的好选择。但是为什么您认为永远等待而不是设置一些最大可接受的超时时间更好呢?更重要的是,如果您想等待(基本上永远)直到所有任务完成,那么就可以简单地给awaitTermination一个非常长的时间,而没有理由去做所有这些逻辑。
ToolmakerSteve

这与此处已经介绍的解决方案没有什么不同。您的公正解决方案与@sjlee提出的解决方案相同
pulp_fiction

不确定根据oracle doc为何需要检查是否完成,invokeAll将仅返回“当所有完成或超时到期时,以先发生者为准”
Mashrur

3

只是为了在此处提供更多使用闩锁/屏障的替代方案。您还可以使用CompletionService获得部分结果,直到全部完成

从Java Concurrency的实践中可以得出以下结论:“如果有大量要提交给执行器的计算,并且想要在它们可用时检索其结果,则可以保留与每个任务相关联的Future,并通过调用get与a来重复轮询以完成操作。超时为零。这是可能的,但很繁琐。幸运的是,还有更好的方法:完成服务。”

在这里执行

public class TaskSubmiter {
    private final ExecutorService executor;
    TaskSubmiter(ExecutorService executor) { this.executor = executor; }
    void doSomethingLarge(AnySourceClass source) {
        final List<InterestedResult> info = doPartialAsyncProcess(source);
        CompletionService<PartialResult> completionService = new ExecutorCompletionService<PartialResult>(executor);
        for (final InterestedResult interestedResultItem : info)
            completionService.submit(new Callable<PartialResult>() {
                public PartialResult call() {
                    return InterestedResult.doAnOperationToGetPartialResult();
                }
        });

    try {
        for (int t = 0, n = info.size(); t < n; t++) {
            Future<PartialResult> f = completionService.take();
            PartialResult PartialResult = f.get();
            processThisSegment(PartialResult);
            }
        } 
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } 
        catch (ExecutionException e) {
            throw somethinghrowable(e.getCause());
        }
    }
}

3

这是我的解决方案,基于“ AdamSkywalker”技巧,并且有效

package frss.main;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestHilos {

    void procesar() {
        ExecutorService es = Executors.newFixedThreadPool(4);
        List<Runnable> tasks = getTasks();
        CompletableFuture<?>[] futures = tasks.stream().map(task -> CompletableFuture.runAsync(task, es)).toArray(CompletableFuture[]::new);
        CompletableFuture.allOf(futures).join();
        es.shutdown();

        System.out.println("FIN DEL PROCESO DE HILOS");
    }

    private List<Runnable> getTasks() {
        List<Runnable> tasks = new ArrayList<Runnable>();

        Hilo01 task1 = new Hilo01();
        tasks.add(task1);

        Hilo02 task2 = new Hilo02();
        tasks.add(task2);
        return tasks;
    }

    private class Hilo01 extends Thread {

        @Override
        public void run() {
            System.out.println("HILO 1");
        }

    }

    private class Hilo02 extends Thread {

        @Override
        public void run() {
            try {
                sleep(2000);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("HILO 2");
        }

    }


    public static void main(String[] args) {
        TestHilos test = new TestHilos();
        test.procesar();
    }
}

2

您可以使用以下代码:

public class MyTask implements Runnable {

    private CountDownLatch countDownLatch;

    public MyTask(CountDownLatch countDownLatch {
         this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
         try {
             //Do somethings
             //
             this.countDownLatch.countDown();//important
         } catch (InterruptedException ex) {
              Thread.currentThread().interrupt();
         }
     }
}

CountDownLatch countDownLatch = new CountDownLatch(NUMBER_OF_TASKS);
ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
for (int i = 0; i < NUMBER_OF_TASKS; i++){
     taskExecutor.execute(new MyTask(countDownLatch));
}
countDownLatch.await();
System.out.println("Finish tasks");

2

我创建了以下工作示例。这个想法是要有一种方法来处理具有多个线程(以编程方式由numberOfTasks / threshold确定)的任务池(我以队列为例),然后等待所有线程完成以继续进行其他处理。

import java.util.PriorityQueue;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/** Testing CountDownLatch and ExecutorService to manage scenario where
 * multiple Threads work together to complete tasks from a single
 * resource provider, so the processing can be faster. */
public class ThreadCountDown {

private CountDownLatch threadsCountdown = null;
private static Queue<Integer> tasks = new PriorityQueue<>();

public static void main(String[] args) {
    // Create a queue with "Tasks"
    int numberOfTasks = 2000;
    while(numberOfTasks-- > 0) {
        tasks.add(numberOfTasks);
    }

    // Initiate Processing of Tasks
    ThreadCountDown main = new ThreadCountDown();
    main.process(tasks);
}

/* Receiving the Tasks to process, and creating multiple Threads
* to process in parallel. */
private void process(Queue<Integer> tasks) {
    int numberOfThreads = getNumberOfThreadsRequired(tasks.size());
    threadsCountdown = new CountDownLatch(numberOfThreads);
    ExecutorService threadExecutor = Executors.newFixedThreadPool(numberOfThreads);

    //Initialize each Thread
    while(numberOfThreads-- > 0) {
        System.out.println("Initializing Thread: "+numberOfThreads);
        threadExecutor.execute(new MyThread("Thread "+numberOfThreads));
    }

    try {
        //Shutdown the Executor, so it cannot receive more Threads.
        threadExecutor.shutdown();
        threadsCountdown.await();
        System.out.println("ALL THREADS COMPLETED!");
        //continue With Some Other Process Here
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
}

/* Determine the number of Threads to create */
private int getNumberOfThreadsRequired(int size) {
    int threshold = 100;
    int threads = size / threshold;
    if( size > (threads*threshold) ){
        threads++;
    }
    return threads;
}

/* Task Provider. All Threads will get their task from here */
private synchronized static Integer getTask(){
    return tasks.poll();
}

/* The Threads will get Tasks and process them, while still available.
* When no more tasks available, the thread will complete and reduce the threadsCountdown */
private class MyThread implements Runnable {

    private String threadName;

    protected MyThread(String threadName) {
        super();
        this.threadName = threadName;
    }

    @Override
    public void run() {
        Integer task;
        try{
            //Check in the Task pool if anything pending to process
            while( (task = getTask()) != null ){
                processTask(task);
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            /*Reduce count when no more tasks to process. Eventually all
            Threads will end-up here, reducing the count to 0, allowing
            the flow to continue after threadsCountdown.await(); */
            threadsCountdown.countDown();
        }
    }

    private void processTask(Integer task){
        try{
            System.out.println(this.threadName+" is Working on Task: "+ task);
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
}
}

希望能帮助到你!



1

您应该使用executorService.shutdown()executorService.awaitTermination方法。

例子如下:

public class ScheduledThreadPoolExample {

    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        executorService.scheduleAtFixedRate(() -> System.out.println("process task."),
                0, 1, TimeUnit.SECONDS);

        TimeUnit.SECONDS.sleep(10);
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.DAYS);
    }

}

在shutdown()/之后需要awaitTermination()/
gaurav

1

因此,如果有人想要一种更简单的方法来执行此操作,我会在此处发布链接问题的答案

ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture[] futures = new CompletableFuture[10];
int i = 0;
while (...) {
    futures[i++] =  CompletableFuture.runAsync(runner, executor);
}

CompletableFuture.allOf(futures).join(); // THis will wait until all future ready.

0

Java 8-我们可以使用流API来处理流。请参见下面的代码段

final List<Runnable> tasks = ...; //or any other functional interface
tasks.stream().parallel().forEach(Runnable::run) // Uses default pool

//alternatively to specify parallelism 
new ForkJoinPool(15).submit(
          () -> tasks.stream().parallel().forEach(Runnable::run) 
    ).get();

2
嗨,弗拉德,欢迎来到StackOverflow。您能否编辑您的答案以解释它如何回答问题以及代码的作用?在此不鼓励仅使用代码的答案。谢谢!
蒂姆·马隆

这篇文章谈论并发。并行!=并发性
GabrielBB

0

ExecutorService WORKER_THREAD_POOL 
  = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
    WORKER_THREAD_POOL.submit(() -> {
        try {
            // doSomething();
            latch.countDown();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

// wait for the latch to be decremented by the two remaining threads
latch.await();

如果doSomething()抛出其他异常,latch.countDown()似乎将不会执行,那么我该怎么办?


0

如果您依次使用更多线程ExecutionServices并想等待每个EXECUTIONSERVICE完成。最好的方法如下所示;

ExecutorService executer1 = Executors.newFixedThreadPool(THREAD_SIZE1);
for (<loop>) {
   executer1.execute(new Runnable() {
            @Override
            public void run() {
                ...
            }
        });
} 
executer1.shutdown();

try{
   executer1.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

   ExecutorService executer2 = Executors.newFixedThreadPool(THREAD_SIZE2);
   for (true) {
      executer2.execute(new Runnable() {
            @Override
            public void run() {
                 ...
            }
        });
   } 
   executer2.shutdown();
} catch (Exception e){
 ...
}

-1

这可能有帮助

Log.i(LOG_TAG, "shutting down executor...");
executor.shutdown();
while (true) {
                try {
                    Log.i(LOG_TAG, "Waiting for executor to terminate...");
                    if (executor.isTerminated())
                        break;
                    if (executor.awaitTermination(5000, TimeUnit.MILLISECONDS)) {
                        break;
                    }
                } catch (InterruptedException ignored) {}
            }

-1

您可以在此Runner类上调用waitTillDone()

Runner runner = Runner.runner(4); // create pool with 4 threads in thread pool

while(...) {
    runner.run(new MyTask()); // here you submit your task
}


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


runner.shutdown(); // once you done you can shutdown the runner

您可以重用该类并在调用shutdown()之前多次调用waitTillDone(),而且您的代码非常简单。另外,您不必预先知道任务数量

要使用它,只需将此gradle / maven compile 'com.github.matejtymes:javafixes:1.3.1'依赖项添加到您的项目中。

更多详情可在这找到:

https://github.com/MatejTymes/JavaFixes


-2

executor中有一个方法getActiveCount()-给出活动线程的数量。

跨线程后,我们可以检查activeCount()值是否为0。一旦该值为零,则表示当前没有正在运行的活动线程,这意味着任务已完成:

while (true) {
    if (executor.getActiveCount() == 0) {
    //ur own piece of code
    break;
    }
}

3
这不是一个好主意,请参阅stackoverflow.com/a/7271685/1166992和javadoc:“返回正在主动执行任务的线程的大概数量。”
Olivier Faucheux
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.