簡體   English   中英

執行器服務不等到所有線程都完成才返回結果

[英]Executor service not waiting until all threads get completed to return the result

我正在使用 Executor 服務來執行一組並行任務,並且在所有任務完成后我需要做一些事情。 但是在我下面的實現中,它並沒有按預期發生,也沒有等到一切都完成。 請參考下面的代碼片段。

public void doParallelStuff() throws InterruptedException {
        
        final int THREAD_COUNT = 5;
        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
        Set<String> myIdSet = Set.of("123", "234", "345", "897", "893"); // actual set is having 20k+ IDs
        CountDownLatch countDownLatch = new CountDownLatch(myIdSet.size());

        myIdSet.forEach(id -> executorService.execute(() -> {
            // retrieve & do processing the entity related to id
            countDownLatch.countDown();
        }));

        countDownLatch.await(10L, TimeUnit.SECONDS);
        executorService.shutdown();

        // do some other stuff after all completed
        // (but this point is getting executed before the all tasks get completed)
    }

失去CountDownLatch

您不需要CountDownLatch 執行器服務提供了該功能,內置。

awaitTermination中的shutdown和等待ExecutorService

關鍵是打兩個電話:

  • ExecutorService#shutdown停止執行器服務接受更多的任務提交。
  • ExecutorService#awaitTermination阻止並等待所有提交的任務完成。

這是從下面發布的完整示例應用程序中摘錄的主要代碼塊。

首先我們生成一些假輸入。 我們定義了一個線程安全列表來保存Result對象,每個提交的任務都會實例化並收集一個新的Result對象。

Set < Integer > ids = IntStream.range( 1 , 20_001 ).boxed().collect( Collectors.toSet() );
List < Result > results = Collections.synchronizedList( new ArrayList <>() );

接下來我們定義一個集合來保存待處理的任務。 我們填充了許多Task對象。

List < Task > tasks = new ArrayList <>( ids.size() );
for ( Integer id : ids ) tasks.add( new Task( id , results ) );

我們創建一個具有合理數量的線程的執行器服務,以適合我們的部署機器。

ExecutorService executorService = Executors.newFixedThreadPool( 7 );

接下來我們定義一個集合來保存我們提交所有任務時執行器服務返回的所有Future對象。

List < Future < Boolean > > futures = null;

提交所有任務,捕捉未來。

try { futures = executorService.invokeAll( tasks , 5 , TimeUnit.MINUTES ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }

👉 在此子例程中進行這兩個關鍵調用,以調用關閉並等待任務完成。

this.shutdownAndAwaitTermination( executorService );  // Blocks here, until either (a) executor service completes all assigned tasks, or (b) time-out fires.

🔑 關鍵概念是上面的代碼會阻塞,直到所有提交的任務都完成……或者直到指定的超時觸發。

最后,我們可以報告工作。

this.report( futures , results );

完整的代碼示例

package work.basil.example.parallel;

import java.time.Instant;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class App
{
    public static void main ( String[] args )
    {
        App app = new App();
        app.demo();
    }

    record Result( Integer id , UUID label , Instant whenCreated ) { }

    class Task implements Callable < Boolean >
    {
        private Integer id;
        private List < Result > results;

        public Task ( Integer id , List < Result > results )
        {
            this.id = id;
            this.results = results;
        }

        @Override
        public Boolean call ( )
        {
            try { Thread.sleep( ThreadLocalRandom.current().nextInt( 0 , 5 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }  // Pretend this method takes a while to do its work.
            Result result = new Result( this.id , UUID.randomUUID() , Instant.now() );
            this.results.add( result );
            return Boolean.TRUE;
        }
    }

    private void demo ( )
    {
        System.out.println( "INFO - Demo starting. " + Instant.now() );

        Set < Integer > ids = IntStream.range( 1 , 20_001 ).boxed().collect( Collectors.toSet() );
        List < Result > results = Collections.synchronizedList( new ArrayList <>() );

        List < Task > tasks = new ArrayList <>( ids.size() );
        for ( Integer id : ids ) tasks.add( new Task( id , results ) );

        ExecutorService executorService = Executors.newFixedThreadPool( 7 );
        List < Future < Boolean > > futures = null;
        try { futures = executorService.invokeAll( tasks , 5 , TimeUnit.MINUTES ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
        this.shutdownAndAwaitTermination( executorService );  // Blocks here, until either (a) executor service completes all assigned tasks, or (b) time-out fires.
        this.report( futures , results );

        System.out.println( "INFO - Demo ending. " + Instant.now() );
    }

    private void report ( final List < Future < Boolean > > futures , final List < Result > results )
    {
        List < Future < Boolean > > failedFutures =
                futures.stream().filter( future -> {
                            try { return future.isCancelled() || ! future.isDone() || Objects.isNull( future.get() ) || Objects.equals( future.get() , Boolean.FALSE ); }
                            catch ( InterruptedException e ) { throw new RuntimeException( e ); }
                            catch ( ExecutionException e ) { throw new RuntimeException( e ); }
                        } )
                        .toList();
        System.out.println( failedFutures.size() + " failed futures:\n" + failedFutures );

        List < Future < Boolean > > doneFutures = futures.stream().filter( future -> {
            try { return future.isDone() && future.get().equals( Boolean.TRUE ); }
            catch ( InterruptedException e ) { throw new RuntimeException( e ); }
            catch ( ExecutionException e ) { throw new RuntimeException( e ); }
        } ).toList();
        System.out.println( doneFutures.size() + " done futures." );

        System.out.println( "First 10 results: " );
        results.stream().limit( 10 ).forEach( System.out :: println );
    }

    // Boilerplate taken from Javadoc of `ExecutorService`, and slightly edited.
    private void shutdownAndAwaitTermination ( ExecutorService executorService )
    {
        executorService.shutdown(); // Disable new tasks from being submitted
        try
        {
            // Wait a while for existing tasks to terminate
            if ( ! executorService.awaitTermination( 5 , TimeUnit.MINUTES ) )
            {
                executorService.shutdownNow(); // Cancel currently executing tasks
                // Wait a while for tasks to respond to being cancelled
                if ( ! executorService.awaitTermination( 5 , TimeUnit.MINUTES ) )
                { System.err.println( "Executor service failed to terminate." ); }
            }
        }
        catch ( InterruptedException ex )
        {
            executorService.shutdownNow();       // (Re-)Cancel if current thread also interrupted
            Thread.currentThread().interrupt();           // Preserve interrupt status
        }
    }
}

運行時:

INFO - Demo starting. 2022-07-22T06:23:19.725428Z
0 failed futures:
[]
20000 done futures.
First 10 results: 
Result[id=7, label=609c5f42-3108-4ed7-a92d-0485016238c7, whenCreated=2022-07-22T06:23:19.765087Z]
Result[id=2, label=5dad4f85-e572-4a06-b226-f402fd3295c3, whenCreated=2022-07-22T06:23:19.765101Z]
Result[id=6, label=97d1786a-830b-42e5-97bb-48e6342fe3b1, whenCreated=2022-07-22T06:23:19.765140Z]
Result[id=4, label=17ae84ac-0dd8-477a-8b67-d4770643a015, whenCreated=2022-07-22T06:23:19.765191Z]
Result[id=5, label=55d4f4ba-9f5d-42f0-9af6-9e6c53159ce9, whenCreated=2022-07-22T06:23:19.765220Z]
Result[id=1, label=2d55c303-8366-441d-a62b-3168158329e0, whenCreated=2022-07-22T06:23:19.765261Z]
Result[id=3, label=ad4c7d04-7917-432e-8ad5-b0312b03d1bd, whenCreated=2022-07-22T06:23:19.765292Z]
Result[id=9, label=2774d965-f179-4b5b-a50f-361f5e4a1a08, whenCreated=2022-07-22T06:23:19.765346Z]
Result[id=13, label=54f48070-2967-4307-b4f9-80d34ece5332, whenCreated=2022-07-22T06:23:19.765378Z]
Result[id=11, label=00899822-591e-4af2-8b32-d496c3997e65, whenCreated=2022-07-22T06:23:19.766582Z]
INFO - Demo ending. 2022-07-22T06:23:27.027545Z

並行流

根據您的具體需求,使用並行流可能是更簡單的方法。 您的控制較少,例如不指定線程數(默認情況下),面對故障時的靈活性較低。 但是你會得到更短更簡單的代碼。

定義一個記錄來保存每個結果。 這與上面類似,但這里我們使用String類型的id更接近您的原始問題。

record Result( String id , UUID label , Instant whenCreated ) { }

創建一些虛假的輸入數據,作為字符串。

List < String > ids =
        IntStream
                .range( 1 , 20_001 )
                .boxed()
                .map( Object :: toString )
                .toList();

並在一行代碼中完成工作。 .parallelStream自動處理獲取一個合理大小的線程池,然后將我們的Result對象實例化為分配給各個線程的任務。

List < Result > results =
        ids
                .parallelStream()
                .map( ( String id ) -> new Result( id , UUID.randomUUID() , Instant.now() ) )
                .toList();

最后,報告結果。

results.stream().limit( 10 ).forEach( System.out :: println );

請注意,使用並行流僅在您有冗長工作負載的情況下才有意義。 對於短暫的工作負載,使用並行流而不是單個流實際上可能需要更多時間,因為在跨線程設置、管理和收集結果方面存在開銷。

項目織機

以上所有內容都與今天的 Java 有關。 如果Project Loom成功了,而且看起來會成功,那么游戲規則將會改變……變得更好。

要了解更多信息,請參閱 Ron Pressler 和 Alan Bateman 等 Loom 團隊成員的帖子、采訪和演示。 關注更新的信息,因為 Loom 已經隨着時間的推移而發展。 並查看用於虛擬線程結構化並發的 Java JEP。 在 Java 19 中可以找到預覽版。

暫無
暫無

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

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