上一篇講解了線程池execute方法詳細執行過程,execute接受Runnable參數但沒有返回執行結果,在需要有執行結果的地方就不太適用了。
異步任務有返回值
如果出現一個計算量稍大用時較長而後面需要結果的情況,可以先把這個任務提交到另外的線程異步執行,主線程仍然繼續做運行等到需要計算結果的時候再來獲取,但是提供異步計算Runnable或者說線程Thread都不返回結果。
但Java中提供了Future與Callable來實現可以獲取異步執行結果的一套框架,在上一篇文章中線程池的execute方法沒有返回值,但是線程池提供的submit方法支持返回值,而submit利用Future與Callable實現。
Future與Callable簡單解釋
Callable接口聲明了一個方法call提供一個泛型的返回結果,
Future接口聲明了5個接口:
cancel方法:取消任務,如果任務取消成功則返回true,如果任務取消失敗則返回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則都返回false。;。
isCancelled方法:表示任務是否已經被取消。
isDone方法:表示任務是否已經完成,若任務完成,則返回true;
get()方法:用來獲取任務結果,這個方法會阻塞線程,會一直等到任務執行完畢才返回;get(long timeout, TimeUnit unit)方法如果在指定時間內,還沒獲取到結果,就直接返回null。
再看線程池的submit
線程池提供了三個submit都繼承至AbstractExecutorService的實現,源碼如下圖:
三個方法接受不同的參數,但都會作為newTaskFor方法的參數生成一個RunnableFuture類的對象,所以最關鍵在於RunnableFuture類。
而RunnableFuture也簡單是一個接口並且繼承了Runnable與Future,所以它可以被當作Runnable用在線程池的execute方法中,而同時又擁有Future的所有功能。
而submit通過newTaskFor生成的實際上是RunnableFuture子類,通過看源碼實際上生成的是RunnableFuture的實現類FutureTask,最終的關鍵就是FutureTask類。
最關鍵類FutureTask詳解
FutureTask實現RunnableFuture,RunnableFuture繼承Runnable與Future,所以可以當作Runnable在線程池中使用,作為execute方法的參數,同時它也是Future的真正實現者,它的關鍵屬性如下圖:
前面提到的Future幾個方法在FutureTask中得到了實現,實現也很簡單,源碼如上圖FutureTask維護一個狀態state,來表示任務狀態取消、中斷、完成等。
我們都是通過Future的get方法來獲取結果,而FutureTask對get方法實現也簡單:
首先判斷state的狀態,如果大於3則拋出異常;
等於3則說明已經完成會執行Thread.yield()讓出CPU;
小於3則說明任務還沒有完成,就會把線程組裝成WaitNode加入到waiters中,並中斷當前線程;
從上一篇知道execute最終是執行參數的run方法,在這裡也就是FutureTask的run方法執行過程如下:
首先執行callable的call方法獲取到返回結果;
CAS方式把狀態從0設置為1;
如果設置成功(保證線程安全)把結果賦值給outcome;
第四步把狀態從1設置為2,表示完成;
最後喚醒waiters鍊表中的線程。
總結
可以看到有返回結果的異步實現最終依賴FutureTask,它同時實現了Runnable與Future,擁有一個Callable屬性。get方法根據FutureTask的狀態會把線程掛起並放到等待鍊表中了。
同時它可以被用到線程池中被執行,在線程池中最後會調用它的run方法,run方法會調用Callable的call方法也就是真正計算的方法,返回結果後會修改FutureTask的狀態並喚醒等待鍊表的線程。
線程池的submit方法還支持Runnable參數,但是FutureTask執行的是Callable的call方法,那麼Runnable中的run是怎麼轉換成Callable的call方法呢?
實際上也很簡單,採用適配器模式,建一個Callable的子類RunnableAdapter,它保存一個Runnable屬性,RunnableAdapter的call方法調用Runnable的run方法,至於返回的結果為null或者自己傳一個結果。
所以要實現有返回結果的異步任務,要麼實現Callable並實現call方法,或者創建一個Runnable的實現類也行。
Java程式設計師日常學習筆記,如理解有誤歡迎各位交流討論!