1. 程式人生 > >淺談Java Future介面

淺談Java Future介面

Java專案程式設計中,為了充分利用計算機CPU資源,一般開啟多個執行緒來執行非同步任務。但不管是繼承Thread類還是實現Runnable介面,都無法獲取任務執行的結果。JDK 5中引入了Callable和Future,通過它們執行非同步任務可以獲取執行結果。
FutureTask分析

JDK 5中獲取任務執行的結果主要是通過FutureTask類實現的。FutureTask實現了RunnableFuture的介面,它既可以作為Runnable被執行緒執行,又可以作為Future得到Callable的返回值。一般結合線程池ExecutorService類使用,大致流程如下: 1. 呼叫執行緒將callable任務submit到執行緒池,返回Future物件 2. 任務被封裝成FutureTask物件
<ignore_js_op>淺談Java Future-1.jpg

 

      執行執行緒呼叫FutureTask的run方法,run主要邏輯是執行Callable任務的call方法獲取結果,並將結果賦值給全域性變數outcome呼叫執行緒呼叫Future物件的get方法來獲取任務執行的結果


上述步驟4中,如果任務沒有執行完成,呼叫get方法時,呼叫執行緒將被阻塞直到任務完成。
假設這樣的一個場景,向Executor批量提交了A、B兩個任務,A任務耗時20ms,B任務耗時10ms,每個任務完成後就能根據結果繼續做後面的事。在該場景中,B任務先完成A任務後完成,由於呼叫執行緒不知道哪個任務會先完成,只能按照任務的提交順序呼叫get方法阻塞獲取結果,最後要耗時20ms才會繼續做A、B任務後面的事。
顯然這種情況下用Future機制是不合適的,B任務先完成了卻增加了額外的等待時間。這個問題的原因是Future沒有提供好的方法去判斷第一個完成的任務。當然你可以通過Future提供的isDone方法輪詢的去判斷第一個完成的任務,但會消耗無謂的CPU資源。
從上面的分析可以看到,單使用Future是不方便的,其主要原因有:一方面沒有提供方法去判斷第一個完成的任務;另一方面是 Future沒有提供Callback機制,只能通過阻塞的get方法去獲取結果。針對第一個問題,JAVA引入了CompletionService介面。
CompletionService分析



CompletionService整合了Executor和BlockingQueue的功能。CompletionService維護一個儲存Future物件的BlockingQueue。只有當這個Future物件狀態是結束的時候,才會加入到這個Queue中。這樣就確保執行時間較短的任務率先被存入佇列中。與Future流程的不同主要是: 1. callable任務提交後,exexute方法執行的是封裝成QueueingFuture的任務物件。QueueingFuture是FutureTask的子類,重寫了done方法,在task執行完成之後將當前future新增到阻塞佇列completionQueue
<ignore_js_op>淺談Java Future-2.jpg
 

      獲取結果是通過CompletionService的take方法和poll方法,這兩個方法都委託給了BlockingQueue,它會在結果不可用時阻塞


<ignore_js_op>淺談Java Future-3.jpg 


由實現可知,Future的回撥行為是在ExecutorCompletionService中實現的,完成的任務會封裝到一個佇列中,供客戶端詢問時使用。Future本身是沒有提供回撥方法的。另外,使用CompletionService雖然能保證任務結果按照完成先後順序排序,但仍存在呼叫阻塞的問題。Guava的ListenableFuture 和JDK8的CompletableFuture對Future進行了擴充套件,當計算結果完成後可以立即執行後續邏輯。
Future的擴充套件

Guava的ListenableFuture擴充套件了Future介面,是一個可以監聽結果的Future。就是它可以監聽非同步執行的過程,執行完了,自動觸發後續操作。常用的新增監聽器的方法是Futures.addCallback方法,大致流程如下: 1.  每當對一個ListenableFuture增加回調時,都會向執行緒池提交監聽任務callbackListener。該任務的run方法會通過getDone方法阻塞獲取future的執行結果。程式碼片段見下:
<ignore_js_op>淺談Java Future-4.jpg 

      獲取結果後立即執行callback的onFailure方法或者onSuccess方法,不會阻塞呼叫


當然ListenableFuture的回撥機制不是通用的,如果主任務非同步執行子任務並且需等待返回結果,此時ListenableFuture的作用和Future幾乎差不多,都是通過get方法阻塞獲取結果。如果主任務不需要等待子任務的返回結果,但子任務一旦計算完成就做其他計算,此時使用ListenableFuture非常合適。
CompletableFuture是JDK1.8新增的的類,提供了非常強大的Future的擴充套件功能。可以對多個非同步處理進行編排,實現更復雜的非同步處理。它能夠將回調放到與任務不同的執行緒中執行,也能將回調作為繼續執行的同步函式,在與任務相同的執行緒中執行。大家可以參考CompletableFuture的API瞭解它提供的功能。
總結

在實際專案中,需要根據具體的業務場景選擇合適的Future工具類來實現非同步程式設計。如果專案中使用Java 8,推薦使用CompletableFuture類,它提供了更多的非同步控制。如果使用之前版本,可以使用Guava等框架提供的Future工具類。

from: https://www.wengbi.com/thread_69744_1.html