1. 程式人生 > >FutureTask底層原理分析

FutureTask底層原理分析

FutureTask實現了介面Future,同Future一樣,代表非同步計算的結果。當然,FutureTask除了實現Future介面之外,還實現了Runnable介面,所以,FutureTask既可以由Executor來排程執行,也可以由排程執行緒呼叫FutureTask.run()直接執行。

FutureTask狀態

根據FutureTask的run方法是否被執行以及是否被執行完成,FutureTask有3種狀態:

  1. 未啟動:run方法被執行前,FutureTask處於未啟動狀態;

  2. 已啟動:run方法被執行的過程中,FutureTask處於已啟動狀態;

  3. 已完成:run方法執行完成後正常結束,或者被取消,或者是執行過程中丟擲異常導致的異常結束,FutureTask處於已完成狀態。

FutureTask狀態轉換

FutureTask狀態轉換可以總結為下圖:

FutureTask狀態轉換圖
  • 當FutureTask處於未啟動或者是已啟動狀態時,此時還未得到執行緒執行結果,呼叫FutureTask.get方法會導致執行緒阻塞;

  • 當FutureTask處於已完成狀態時,此時已經得到執行緒執行結果,呼叫FutureTask.get方法會立即返回執行緒執行結果;

  • 當FutureTask處於未啟動狀態時,呼叫FutureTask.cancel方法將會導致該task永遠不會被執行;

  • 當FutureTask處於啟動狀態時,呼叫FutureTask.cancel方法將會中斷該任務的執行,至於會不會對任務產生影響由cancel方法的入參決定;

  • 當FutureTask處於已完成狀態,呼叫FutureTask.cancel方法返回false。

接下來就以run、get和cancel方法為切入點分析FutureTask具體實現。

FutureTask原始碼分析

在開始分析原始碼之前,我們先來看看FutureTask的成員變數:

成員變數
  1. state:記錄task狀態,可取值為0~6;

  2. callable:task實際載體,run方法實際呼叫callable.call();

  3. outcome:執行緒執行任務結束後的返回結果;

  4. runner:記錄執行task的執行緒;

  5. waiters:等待task執行結果的執行緒佇列。

構造方法
構造方法

FutureTask提供兩個構造方法來封裝Callable和Runnable,當構造方法傳入引數為Runnable,會通過Executors.callable方法將其轉換成Callable。

Executors.callable方法實現
get方法實現

FutureTask提供帶超時時間的get和不到超時時間的get:

get方法實現

對比帶超時時間和不帶超時時間的get方法實現,最為重要的實現就是等待直到task狀態變為已完成狀態或者等待時間超過超時時間,對應到原始碼就是加紅框的awaitDone方法。接下來我們來具體分析一下awaitDone方法到底是如何來實現執行緒阻塞等待的。

awaitDone方法實現

awaitDone實現 具體的執行流程如下:
  1. 計算等待時間deadline,如果是帶超時時間的get,deadline = 當前時間 + 等待時間,如果是不帶超時時間的get,deadline = 0;

  2. 判斷執行緒是否中斷,如果執行緒中斷,將當前執行緒從等待佇列waiters中移除,丟擲中斷異常,否則,跳轉到步驟3;

  3. 獲取task狀態state:

  • 如果task狀態為已完成狀態,將等待執行緒節點的執行緒置為null,返回state

  • 如果task狀態為正在執行,呼叫Thread.yield()將執行緒從執行狀態變為可執行狀態;

  • 否則,跳轉到步驟4;

  1. 如果等待執行緒節點q為null,初始化等待執行緒節點q,否則,跳轉到步驟5;

  2. 如果當前等待執行緒節點q還未成功進入等待佇列waiters,進入執行緒等待佇列,否則,跳轉到步驟6;

  3. 判斷是否是帶超時時間的get:

  • 如果是帶超時時間get,判斷當前是否超時,如果已經超時,將當前等待節點q從waiters中移出,返回task狀態state,如果還未超時,呼叫LockSupport.parkNanos方法阻塞當前執行緒;

  • 否則,跳轉到步驟7;

  1. 呼叫LockSupport.park方法,阻塞當前執行緒,然後跳轉到步驟2。

從get方法整個流程可以看出:

  • FutureTask維護一個等待執行緒佇列waiters,如果task還未執行完畢,呼叫get方法的執行緒會先進入等待佇列自旋等待;

  • awaitDone方法其實是個死迴圈,直到task狀態變為已完成狀態或者等待時間超過超時時間或者執行緒中斷才會跳出迴圈,程式結束;

  • 為了節省開銷,執行緒不會一直自旋等待,而是會阻塞,使用LockSupport的park系列方法實現執行緒阻塞;

run方法實現
run方法實現

具體執行流程如下:

  1. 判斷task狀態,如果task還未執行,跳轉到步驟2,否則,返回,程式結束;

  2. 通過CAS設定執行task的執行緒,設定成功,跳轉到步驟3,否則,返回,程式結束;

  3. 執行callable.call方法,呼叫set方法設定call方法返回結果以及task狀態;

  4. 設定當前運行當前task的執行緒為null;

  5. 判斷當前task狀態,如果task狀態為正在中斷或者已中斷,呼叫Thread.yield()將執行緒從執行狀態變為可執行狀態。

set方法實現

set方法實現 set方法主要乾了這兩件事:
  1. 設定返回結果outcome以及task狀態state;

  2. 呼叫finishCompletion方法操作等待佇列waiters中的等待執行緒。

finishCompletion實現

finishCompletion實現 整個finishCompletion方法清除和喚醒了等待佇列中的等待執行緒,呼叫get方法被阻塞的執行緒也就是在這裡呼叫LockSupport.unpark方法被喚醒的。
cancel方法實現
cancel方法實現
  1. 判斷task狀態,如果不為未啟動狀態,返回false,程式結束,否則,跳轉到步驟2;

  2. 判斷入參mayInterruptIfRunning:

  • true:CSA設定state為正在中斷,設定失敗返回false,否則中斷正在執行task的執行緒,CAS設定state為已中斷;

  • false:CSA設定state為已取消,設定失敗返回false,需要注意的是,正在執行task的執行緒是不會中斷的,換句話說,入參為false時不會對task的執行有任何影響。

  • 注:根據程式碼實現:

- 處於啟動狀態的task,呼叫cancel方法是否會對task的執行有所影響完全依賴於cancel方法的入參,true時會有影響,false時不會有影響; - 處於未啟動狀態的task,呼叫cancel方法後,該task將不會再被執行。

  1. 呼叫finishCompletion方法清除和喚醒等待佇列waiters中的等待執行緒,返回true,程式結束。

從get、run、cancel方法的實現,FutureTask的執行緒等待與喚醒可以總結為下圖:

FutureTask執行緒等待喚醒

後記

到這裡為止,FutureTask的原始碼就分析就結束了。做一個簡短的總結:

  1. FutureTask是通過LockSupport來阻塞執行緒、喚醒執行緒;

  2. 對於多執行緒訪問成員變數waiters、state,都採用CAS來操作;

總的來說,FutureTask是一個非常好的CAS和LockSupport搭配使用的例子。

作者:miaoLoveCode 連結:https://www.jianshu.com/p/06f8df545a86 來源:簡書 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。