Java FutureTask 源碼解析

FutureTask註釋

FutureTask一個可取消的異步計算。利用開始和取消計算的方法、查詢計算是否完成的方法和獲取計算結果的方法,此類提供了對Future的基本實現。僅在計算完成時才能獲取結果;如果計算尚未完成,則阻塞get方法。一旦計算完成,就不能在重新開始或取消計算。

可使用FutureTask包裝Callable或Runnable對象。因爲Future實現了Runnable,所以可將Future提交給Executor執行。

FutureTask屬性

FutureTask 有兩個很重要的屬性 分別是 state  runner ,futureTask之所以可以支持cancel操作 就是因爲這兩個屬性
其中state爲枚舉值:
NEW                            新建            0
COMPLETING            執行中        1
NORMAL                   正常            2
EXCEPTIONAL           異常            3
CANCELLED               取消           4
INTERRUPTING         中斷中        5
INTERRUNPED          被中斷        6

state的狀態變化可以有四種方式
NEW->COMPLETING->NORMAL                        正常完成的流程
NEW->COMPLETING->EXCEPTIONAL                出現異常的流程
NEW->CANCELED                                                  被取消
NEW->INTERRUNPING->INTERRRUNPTED       被中斷

我們研究下Task的狀態變化也就是一個任務的生命週期:

我們創建一個FutureTask 首先會調用構造方法:
  1. public FutureTask(Runnable runnable, V result) {
  2. this.callable = Executors.callable(runnable, result);
  3. this.state = NEW; // ensure visibility of callable
  4. }
在我們構造Task的時候會把狀態 設置成 NEW 也就是所有 狀態變化路徑的起始狀態

我們創建完一個Task 會提交給Executes來執行(當然我們也可以自己啓動Thread來執行 效果基本是一樣,只是交給線程池執行Task可能會延遲執行)。
在之後的Task生命週期的變化 主要取決於 run()方法先被調用還是cancel ()方法會被調用,這兩個方法的執行順序決定了Task的生命週期的四種走向。

FutureTask run()

我們先分析run方法先被調用的情況,爲了能對run()方法能更加詳細的理解我在run方法中加了增加了些註釋
  1. public void run() {
  2. 1、首先判斷任務的狀態 如果任務的狀態不是new 說明任務的狀態已經改變(說明他已經走了4種可能變化的一種) 
  3.         2、如果狀態是new就會把 當前執行任務的線程付給runner, 這裏用的cmpandset如果runner不爲空 說明已經有線程在執行 
  4.         3、任務也會退出執行,如果狀態是new並且runner爲空並且把當前的線程付給了runner那麼就繼續執行任務(runner state 都是 volatile 類型的變量是一個很輕量機的線程安全操作) 
  5.         4、引起state狀態變化的原因 就是調用了cancel 或是 run 
  6. if (state != NEW ||
  7. !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
  8. return;
  9. //開始執行任務  
  10. try {
  11. Callable<V> c = callable;
  12. // 如果 要執行的任務不爲空 並且狀態 new 就執行 
  13. if (c != null && state == NEW) {
  14. V result;
  15. boolean ran;
  16. try {
  17. // 執行任務  
  18. result = c.call();
  19. // 如果沒有意外發生就執行成功了  
  20. ran = true;
  21. } catch (Throwable ex) {
  22. // 有異常  
  23. result = null;
  24. ran = false;
  25. // 設置異常
  26. setException(ex);
  27. }
  28. // 如果計算成功了 設置結果
  29. if (ran)
  30. set(result);
  31. }
  32. } finally {
  33. // runner must be non-null until state is settled to
  34. // prevent concurrent calls to run()
  35. // 不管是否執行成功了 都把runner設置成null  
  36. runner = null;
  37. // state must be re-read after nulling runner to prevent
  38. // leaked interrupts
  39. int s = state;
  40. if (s >= INTERRUPTING)
  41. handlePossibleCancellationInterrupt(s);
  42. }
  43. }
Task執行後如果成功會調用set()方法,如果有異常會調用setException()方法。

我們先看下set方法 :
  1. protected void set(V v) {
  2. // 如果state是new 把state設置成 COMPLETING 
  3. if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
  4. // 將結果保存到outcome
  5. outcome = v;
  6. // 將任務設置成NORMAL   over the task  
  7. U.putOrderedInt(this, STATE, NORMAL); // final state
  8. finishCompletion();
  9. }
  10. }
如果現在的狀態是NEW 就把狀態設置成COMPLETING 然後設置成NORMAL。 這個執行流程導致的狀態變化就是
NEW->COMPLETING->NORMAL 

執行步驟是 首先執行 run() 並且Task正常完成而且在這其間沒有調用cancel()

上邊是任務正常執行完成的狀態變化,我們在看下有異常的情況。有異常的話會調用setException()方法:
  1. protected void setException(Throwable t) {
  2. // 如果state是new 把state設置成 COMPLETING 
  3. if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
  4. outcome = t;
  5. // 將任務設置成EXCEPTIONAL
  6. U.putOrderedInt(this, STATE, EXCEPTIONAL); // final state
  7. finishCompletion();
  8. }
  9. }
如果現在的狀態是NEW 就把狀態設置成COMPLETING 然後設置成EXCEPTIONAL。 這個執行流程導致的狀態變化就是
NEW->COMPLETING->EXCEPTIONAL  

執行步驟是 首先執行 run() 並且Task拋出異常而且在這其間沒有調用cancel()。

上文所分析的場景只是run()方法被調用了而在run()方法執行的過程中 調用cancel()並沒有分析,兩個方法有時間交集的情況我們稍後分析。

FutureTask cancel()

現在我們分析下cancel()方法先被調用的情況
  1. public boolean cancel(boolean mayInterruptIfRunning) {
  2. // mayInterruptIfRunning 是否中斷running
  3. // 這個判斷邏輯等價於:
  4. // if(state!=new || !UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)) 
  5.         // 1、如果state不是new 那麼就退出方法,這時的任務是已經完成了 或是被取消了 或是被中斷了 
  6.         // 2、如果是state是new 就設置state 爲中斷狀態 或是取消狀態 
  7. if (!(state == NEW &&
  8. U.compareAndSwapInt(this, STATE, NEW,
  9. mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
  10. return false;
  11. try { // in case call to interrupt throws exception
  12. // 如果是可中斷 那麼就調用系統中斷方法 然後把狀態設置成INTERRUPTED  
  13. if (mayInterruptIfRunning) {
  14. try {
  15. Thread t = runner;
  16. if (t != null)
  17. t.interrupt();
  18. } finally { // final state
  19. U.putOrderedInt(this, STATE, INTERRUPTED);
  20. }
  21. }
  22. } finally {
  23. finishCompletion();
  24. }
  25. return true;
  26. }
這個方法很簡單 :
1、.如果是cancel(false) 那麼Task的狀態變化就是
NEW->=CANCELLED

2、如果是cancel(true)那麼Task的狀態化就是
NEW->INTERRUPTING ->INTERRUPTED

至此Task的四種狀態變化我們都看到了,不過這都是在兩個方法都是單獨執行的情況。

我們在分析下兩個方法交叉執行的情況( run()->cancel() ):
1、如果Task已經執行(run)然後再調用cancel():
    A、調用cancel(false)情況
  • 如果Task已經在執行而callable.call()沒有返回 或是 call()已經返回但是state狀態還沒有改變,那麼任務調用cancel(false) 不會對任務的執行造成影響 只會影響task的狀態 。
  • 如果callable.call()已經返回並且狀態已經變成COMPLETING或是 COMPLED 那麼對任務執行 和任務狀態都沒有影響。
    B、調用cancel(true)
  • 如果任務已經在執行而callable.call()沒有返回,會把state設置成 INTERRUPTING然後調用執行線程的中斷請求 然後把狀態設置成INTERRUPTED,這裏如果 callable.call()方法可以響應中斷 可能對任務執行產生影響,如果方法不會響應中斷不會對任務運行產生影響。影響任務的狀態。
  • 如果任務已經在執行並且 call()已經返回但是state狀態還沒有改變  不會對任務的執行造成影響 只會影響任務的狀態 。
2、調用cancel()後 在執行任務 ( cancel() -> run() )
先調用cancel()無論是那種調用方式都會引起state狀態的變化。在run()方法執行的時候發現state已經不是new了 就會放棄任務的執行。

任務結果回調方法 done()

不管是run,還是cancel,最後都會調用finishCompletion()方法。
  1. private void finishCompletion() {
  2. // assert state > COMPLETING;
  3. for (WaitNode q; (q = waiters) != null;) {
  4. if (U.compareAndSwapObject(this, WAITERS, q, null)) {
  5. for (;;) {
  6. Thread t = q.thread;
  7. if (t != null) {
  8. q.thread = null;
  9. LockSupport.unpark(t);
  10. }
  11. WaitNode next = q.next;
  12. if (next == null)
  13. break;
  14. q.next = null; // unlink to help gc
  15. q = next;
  16. }
  17. break;
  18. }
  19. }
  20. // 子類覆寫此方法,在計算結果結束後會回調此方法,獲取結果的方法:get()
  21. done();
  22. callable = null; // to reduce footprint
  23. }

獲取結果的方法 get()

  1. public V get() throws InterruptedException, ExecutionException {
  2. int s = state;
  3. // 如果沒有計算完成,則等待
  4. if (s <= COMPLETING)
  5. s = awaitDone(false, 0L);
  6. return report(s);
  7. }
  1. private V report(int s) throws ExecutionException {
  2. // 獲取到結果
  3. Object x = outcome;
  4. // 如果是正常,則返回結果
  5. if (s == NORMAL)
  6. return (V)x;
  7. // 如果是異常,則拋出異常 CancellationException
  8. if (s >= CANCELLED)
  9. throw new CancellationException();
  10. throw new ExecutionException((Throwable)x);
  11. }

























發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章