簡體   English   中英

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

[英]How to wait for all threads to finish, using ExecutorService?

我需要一次執行一些任務 4,如下所示:

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

一旦所有這些都完成,我如何得到通知? 現在我想不出比設置一些全局任務計數器並在每個任務結束時減少它更好的方法,然后在無限循環中監視這個計數器變為 0; 或獲取期貨列表,並在無限循環中監控所有期貨。 什么是不涉及無限循環的更好解決方案?

謝謝。

基本上在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) {
  ...
}

使用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();

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);

您也可以使用期貨列表:

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() 。 因此,一旦最后一個線程完成,您就可以保證“繼續”並通過此塊。 需要說明的是,由於獲得()調用重新引發異常,如果線程模具一個,你會從這個提高可能先於另一個線程完成完成[避免這種情況,你可以添加一個catch ExecutionException各地接電話]。 另一個警告是它保留對所有線程的引用,因此如果它們具有線程局部變量,則在您通過此塊之前它們不會被收集(盡管您可能能夠解決這個問題,如果它成為一個問題,通過刪除Future 不在 ArrayList 中)。 如果你想知道哪個 Future“先完成”,你可以使用類似https://stackoverflow.com/a/31885029/32453 的東西

在 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();

只有我的兩分錢。 為了克服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();

游戲有點晚了,但為了完成...

與其“等待”所有任務完成,您可以根據好萊塢原則思考,“不要給我打電話,我會打電話給你”——當我完成時。 我認為生成的代碼更優雅......

Guava 提供了一些有趣的工具來實現這一點。

一個例子:

將一個 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) {
        // do something with all the results
    }

    @Override
    public void onFailure(Throwable t) {
        // log failure
    }
});

這也提供了一個優勢,即一旦處理完成,您就可以在一個地方收集所有結果......

更多信息 在這里

Java 5 及更高版本中的CyclicBarrier類就是為這種事情設計的。

這里有兩種選擇,只是有點混淆哪個最好去。

選項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(); 在 try catch 中是個好主意,對嗎?

遵循以下方法之一。

  1. 遍歷所有Future任務,從ExecutorService submit返回,並按照Kiran建議在Future對象上通過阻塞調用get()檢查狀態
  2. ExecutorService上使用invokeAll()
  3. 倒計時鎖存器
  4. ForkJoinPoolExecutors.html#newWorkStealingPool
  5. 按順序使用 ThreadPoolExecutor 的shutdown, awaitTermination, shutdownNow API

相關 SE 問題:

Java 多線程中如何使用 CountDownLatch?

如何正確關閉java ExecutorService

您可以將您的任務包裝在另一個 runnable 中,它將發送通知:

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

我剛剛編寫了一個示例程序來解決您的問題。 沒有給出簡潔的實現,所以我會添加一個。 雖然您可以使用executor.shutdown()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();
    }

只是為了在這里提供更多的替代方案,而不是使用閂鎖/屏障。 您還可以獲得部分結果,直到所有結果都使用CompletionService 完成

來自實踐中的 Java 並發:“如果您有一批計算要提交給 Executor,並且您想在它們可用時檢索它們的結果,您可以保留與每個任務關聯的 Future,並通過調用 get 並重復輪詢完成超時為零。這是可能的,但很乏味。幸運的是,有更好的方法:完成服務。”

這里的實現

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());
        }
    }
}

這是我的解決方案,基於“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();
    }
}

ExecutorService 的清潔方式

 List<Future<Void>> results = null;
 try {
     List<Callable<Void>> tasks = new ArrayList<>();
     ExecutorService executorService = Executors.newFixedThreadPool(4);
     results = executorService.invokeAll(tasks);
 } catch (InterruptedException ex) {
     ...
 } catch (Exception ex) {
     ...
 }

您可以使用此代碼:

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");

所以我在這里發布鏈接問題的答案,以防有人想要一種更簡單的方法來做到這一點

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.

我創建了以下工作示例。 這個想法是有一種方法來處理具有許多線程(由 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();
        }
    }
}
}

希望能幫助到你!

您可以使用您自己的ExecutorCompletionService子類來包裝taskExecutor ,並使用您自己的BlockingQueue實現來在每個任務完成時獲得通知,並在完成的任務數量達到您想要的目標時執行您想要的任何回調或其他操作。

你應該使用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);
    }

}

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();

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()似乎不會執行,那我該怎么辦?

如果您按順序使用更多線程 ExecutionServices 並希望等待 EACH 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){
 ...
}

使用Project LoomAutoCloseable執行程序服務上嘗試使用資源語法

Project Loom試圖為 Java 的並發能力添加新功能。

其中一項功能是使ExecutorService AutoCloseable 這意味着每個ExecutorService實現都將提供一個close方法。 這意味着我們可以使用try-with-resources語法來自動關閉ExecutorService對象。

ExecutorService#close方法會阻塞,直到所有提交的任務完成。 使用close代替調用shutdown & awaitTermination

AutoCloseable有助於 Project Loom 將“結構化並發”引入 Java 的嘗試。

try (
    ExecutorService executorService = Executors.… ;
) {
    // Submit your `Runnable`/`Callable` tasks to the executor service.
    …
}
// At this point, flow-of-control blocks until all submitted tasks are done/canceled/failed.
// After this point, the executor service will have been automatically shutdown, wia `close` method called by try-with-resources syntax.

有關 Project Loom 的更多信息,請搜索 Ron Pressler 和 Project Loom 團隊其他人的演講和采訪。 隨着 Project Loom 的發展,請關注最近的項目。

基於早期訪問Java 17的 Project Loom 技術的實驗版本現已可用

這可能有幫助

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) {}
            }

你可以在這個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

執行程序getActiveCount()有一個方法 - 提供活動線程的計數。

跨越線程后,我們可以檢查activeCount()值是否為0 一旦該值為零,則表示當前沒有活動線程正在運行,這意味着任務已完成:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM