在討論兩者之間的區別以前,先交待一下背景。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 與 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章。