CompletionService 與 ExecutorService 獲取任務執行結果時的區別

CompletionService 與 ExecutorService 之間的區別

在討論兩者之間的區別以前,先交待一下背景。html

看了ElasticSearch Transport模塊的源碼,裏面充滿了各類異步回調獲取結果,因而就想:爲何不用Callable接口,而後再基於java.util.concurrent.Future#get()獲取任務的執行結果呢?java

又由於ES的Transport模塊底層是基於Netty實現的,研究了下Netty的獲取線程執行結果的方式,,雖然Callable、FutureTask 將提交任務執行"異步化"了,可是在獲取任務執行結果的這一步,JDK Future#get() 是阻塞的(超時阻塞),那麼,能不能在獲取結果的時候也不阻塞呢?有二種渠道實現:編程

  • 回調機制併發

    ElasticSearch裏面就是大量用到回調機制。因爲JDK Future的缺陷,Netty的 ChannelFuture擴展了JDK 的Future接口,並提供了回調機制支持異步獲取任務的執行結果。它的源碼:io.netty.channel.ChannelFuture的註釋很是值得一讀。異步

  • JDK8 裏面提供的java.util.concurrent.CompletableFuture網站

    CompletableFuture 可參考《JAVA8實戰》中瞭解一下.net

固然,本文不打算討論,獲取任務執行結果也不阻塞的具體實現方法,而是"先退一步",來分析下:線程

  • 使用 CompletionService 的 submit 方法 java.util.concurrent.CompletionService#submit(java.util.concurrent.Callable )提交 多個任務,如何獲取任務的執行結果?
  • 使用 ExecutorService 的submit 方法 java.util.concurrent.ExecutorService#submit(java.util.concurrent.Callable )提交 多個任務,如何獲取任務的執行結果?

爲何強調多個任務,由於這裏討論的是多個任務的併發執行。並非第一個任務執行完成後,才能執行第二個任務。那CompletionService 與 ExecutorService 在獲取任務結果的時候的區別是什麼?netty

先說下結論,若是咱們的目標是儘快處理任務的執行結果,而不是必須等到全部的任務都執行完成後,拿到全部的執行結果,才能進行下一步處理,那麼使用 CompletionService 是很是有好處的。code

舉個例子:一個網站要顯示10幅圖像,下載完一幅就顯示一幅,而不須要將這10幅都下載下來,再統一顯示,那就很適合用CompletionService。下載 就是線程要執行的任務,圖像 就是任務的執行結果。

使用ExecutorService時,代碼是這樣的:

//保存 Future<Image>,後面遍歷 List 獲取 Future 結果
List<Future<Image>> futureList = new ArrayList();
for(int i = 0; i < 10; i++)
{
    Future<Image> imageFuture = executorService.submit(downloadTask);//10個下載任務同時併發
    futureList.add(imageFuture);
}

//獲取10個任務的執行結果
for(int i = 0; i < 10; i++)
{
  Future<Imapge> future = futureList.get(i);
  Imapge image = future.get();//若是圖像還沒有下載完成,這裏會阻塞
  render(image);//將已經下載好的圖像渲染到界面
}

咱們是用List<Future<Image>>保存全部的任務Future,而後在for循環裏面遍歷List獲取結果,假設第一個任務下載第一幅圖像,第二個任務下載第2幅圖像,以此類推....

這裏的問題是:若第一幅圖像未下載完成,可是第2幅、第3幅圖像已經下載完了,咱們也沒法優先獲取第2幅、第3幅圖像。也就沒法將已經先下載下來的圖像渲染到界面。

總結起來說就是:提交任務,將任務添加到List裏面的順序,與任務實際完成順序是不相關的。

而使用 CompletionService,就能解決這個缺陷。它使得咱們可以得到那些最早下載好的圖像。

使用 CompletionService時,代碼是這樣的:

for(int i = 0; i < 10; i++)
{
    completionService.submit(downloadTask);//10個下載任務同時併發
}

 for(int i = 0; i<10;i++ )
 {    
    //只要任一幅圖像下載下來了,completionService.take()就會返回,從而 get() 到這幅圖像
    render(completionService.take().get());
}

completionService.take()是個阻塞方法,若是10幅圖像中都沒下載下來,那就阻塞了。但只要有一幅下載下來了,就當即能得到到這幅圖像。顯然,這裏:獲取任務的執行結果的順序與提交任務的順序無關了。

這裏的實現思路也可參考《Java併發編程實戰》中第6章。

原文:http://www.javashuo.com/article/p-pixotixi-x.html

相關文章
相關標籤/搜索