天天看點

Java并發程式設計的藝術(九)——批量擷取多條線程的執行結果 方法一:自己維護傳回結果 方法二:使用ExecutorService的invokeAll函數 方法三:使用CompletionService

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/qq_34173549/article/details/79612353

當向線程池送出callable任務後,我們可能需要一次性擷取所有傳回結果,有三種處理方法。

方法一:自己維護傳回結果

// 建立一個線程池
ExecutorService executorService = Executors.newFixedThreadPool(10);

// 存儲執行結果的List
List<Future<String>> results = new ArrayList<Future<String>>();

// 送出10個任務
for ( int i=0; i<10; i++ ) {
    Future<String> result = executorService.submit( new Callable<String>(){
        public String call(){
            int sleepTime = new Random().nextInt(1000);
            Thread.sleep(sleepTime);
            return "線程"+i+"睡了"+sleepTime+"秒";
        }
    } );
    // 将執行結果存入results中
    results.add( result );
}

// 擷取10個任務的傳回結果
for ( int i=0; i<10; i++ ) {
    // 擷取包含傳回結果的future對象
    Future<String> future = results.get(i);
    // 從future中取出執行結果(若尚未傳回結果,則get方法被阻塞,直到結果被傳回為止)
    String result = future.get();
    System.out.println(result);
}           
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

此方法的弊端:

  1. 需要自己建立容器維護所有的傳回結果,比較麻煩;
  2. 從list中周遊的每個Future對象并不一定處于完成狀态,這時調用get()方法就會被阻塞住,如果系統是設計成每個線程完成後就能根據其結果繼續做後面的事,這樣對于處于list後面的但是先完成的線程就會增加了額外的等待時間。

方法二:使用ExecutorService的invokeAll函數

本方法能解決第一個弊端,即并不需要自己去維護一個存儲傳回結果的容器。當我們需要擷取線程池所有的傳回結果時,隻需調用invokeAll函數即可。 

但是,這種方式需要你自己去維護一個用于存儲任務的容器。

// 建立一個線程池
ExecutorService executorService = Executors.newFixedThreadPool(10);

// 建立存儲任務的容器
List<Callable<String>> tasks = new ArrayList<Callable<String>>();

// 送出10個任務
for ( int i=0; i<10; i++ ) {
    Callable<String> task = new Callable<String>(){
        public String call(){
            int sleepTime = new Random().nextInt(1000);
            Thread.sleep(sleepTime);
            return "線程"+i+"睡了"+sleepTime+"秒";
        }
    };
    executorService.submit( task );
    // 将task添加進任務隊列
    tasks.add( task );
}

// 擷取10個任務的傳回結果
List<Future<String>> results = executorService.invokeAll( tasks );

// 輸出結果
for ( int i=0; i<10; i++ ) {
    // 擷取包含傳回結果的future對象
    Future<String> future = results.get(i);
    // 從future中取出執行結果(若尚未傳回結果,則get方法被阻塞,直到結果被傳回為止)
    String result = future.get();
    System.out.println(result);
}           
  • 28
  • 29
  • 30
  • 31

方法三:使用CompletionService

CompletionService内部維護了一個阻塞隊列,隻有執行完成的任務結果才會被放入該隊列,這樣就確定執行時間較短的任務率先被存入阻塞隊列中。

ExecutorService exec = Executors.newFixedThreadPool(10);

final BlockingQueue<Future<Integer>> queue = new LinkedBlockingDeque<Future<Integer>>(  
                10);  
        //執行個體化CompletionService  
        final CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(  
                exec, queue); 

// 送出10個任務
for ( int i=0; i<10; i++ ) {
    executorService.submit( new Callable<String>(){
        public String call(){
            int sleepTime = new Random().nextInt(1000);
            Thread.sleep(sleepTime);
            return "線程"+i+"睡了"+sleepTime+"秒";
        }
    } );
}

// 輸出結果
for ( int i=0; i<10; i++ ) {
    // 擷取包含傳回結果的future對象(若整個阻塞隊列中還沒有一條線程傳回結果,那麼調用take将會被阻塞,當然你可以調用poll,不會被阻塞,若沒有結果會傳回null,poll和take傳回正确的結果後會将該結果從隊列中删除)
    Future<String> future = completionService.take();
    // 從future中取出執行結果,這裡存儲的future已經擁有執行結果,get不會被阻塞
    String result = future.get();
    System.out.println(result);
}