JUC原始碼學習筆記7——FutureTask原始碼解析,人生亦如是,run起來才有結果

Cuzzz發表於2023-01-03

系列文章目錄和關於我

一丶我們在哪裡會使用到FutureTask

基本上工作中和Future介面 打交道比較多,比如執行緒池ThreadPoolExecutor#sumbit方法,返回值就是一個Future(實際上基本上就是一個FutureTask)。ThreadPoolExecutor#sumbit需要傳入一個Callable,我們作為呼叫方法,在其中的call方法編寫業務邏輯,然後ThreadPoolExecutor會將其包裝為一個FutureTask 提交到執行緒池中(非同步),並立馬返回FutureTask,從而讓呼叫方可以取消任務,檢視任務是否執行結束,獲取非同步任務結果(ThreadPoolExecutor#excute類似,傳入的Runnable被包裝成FutureTask<Void>

FutureTask就如同一個紐帶,連線了任務 和 任務的結果

二丶何為FutureTask

FutureTask原始碼註釋中寫到:

FutureTask是可取消的非同步任務。此類提供Future的基本實現,包括啟動,取消、查詢以檢視任務是否完成以及獲取任務結果的方法。只有在任務完成後才能檢索結果,如果尚未完成,get方法將阻塞。任務完成後,無法重新啟動或取消任務(除非使用runAndReset呼叫任務)。

FutureTask可用於包裝Callable或Runnable物件。因為FutureTask實現了Runnable,所以FutureTask可以提交給Executor執行。

三丶FutureTask繼承關係

image-20230102213501620

1.Future

Future 表示非同步任務的執行結果,它定義了取消、查詢以檢視任務是否完成以及獲取任務結果的方法。只有在任務完成後才能檢索結果,如果尚未完成,get方法將阻塞。任務完成後,無法重新啟動或取消任務(除非使用runAndReset呼叫任務)

方法 解釋
boolean cancel(boolean mayInterruptIfRunning) 嘗試取消此任務的執行。如果任務已完成、已被取消或由於某些其他原因無法取消,則此嘗試將失敗。如果成功且在呼叫取消時此任務尚未啟動,則任務不會執行。如果任務已經開始,則 mayInterruptIfRunning 引數確定是否應該中斷執行該任務的執行緒以嘗試停止該任務。此方法返回後,對 isDone 的後續呼叫將始終返回 true。如果此方法返回 true,則對 isCancelled 的後續呼叫將始終返回 true。
boolean isCancelled() 如果此任務在正常完成之前被取消,則返回 true
boolean isDone() 如果此任務完成,則返回 true。完成可能是由於正常終止、異常或取消——在這些情況下,此方法都將返回 true。
V get() throws InterruptedException, ExecutionException 如果任務沒有執行完,那麼一直阻塞直到任務完成,直到任務執行成功,或者執行失敗,或者執行任務執行緒被中斷
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException get()的超時等待版本,指定當前執行緒等待時間,如果超時任然沒有執行結束,那麼丟擲TimeoutException

2.RunnableFuture

"可以執行的未來",即表示一個可以執行的任務(是一個Runnable),也表示非同步任務的結果(是一個Future)

四丶帶著問題閱讀原始碼

小小FutureTask牛在哪兒暱,為什麼是doug lea寫的,我不行?

  • get方法呼叫的時候,如果任務沒有成功,怎麼實現呼叫執行緒的阻塞
  • get方法出現異常,或者正常結束的時候,怎麼喚醒呼叫get方法的執行緒
  • 在任務執行之前,如果任務被取消那麼將無法執行,已經開始執行的任務只能嘗試中斷執行任務的執行緒,無法取消,那麼doug lea 怎麼判斷執行和取消的先後順序(注意執行和取消可以是多個執行緒呼叫)怎麼處理這裡的執行緒不安全問題

五丶FutureTask的屬性和內部類

1.狀態

為多個執行緒對FutureTask執行狀態的可見性,FutureTask具備屬性private volatile int state,使用volatile修飾,可取以下值(Future定義了對應的常量):

常量 解釋
NEW 任務處於新建狀態
COMPLETING 任務正在完成,意味著非同步計算結束,但是結果沒有寫回到 outcome屬性上
NORMAL 任務正常結束,說明程式設計師定義的業務邏輯,沒有丟擲異常
EXCEPTIONAL 任務執行失敗,說明程式設計師定義的業務邏輯丟擲異常
CANCELLED 任務被取消,說明呼叫方法呼叫了cancel方法,在任務執行之前取消了任務
INTERRUPTING 任務正在被中斷,是一個瞬態,使用cancel(true),呼叫方正在中斷執行任務的執行緒
INTERRUPTED 任務已經被中斷

1.1什麼是COMPLETING,正在完成是什麼鬼

FutureTask使用outcome屬性記錄任務執行結果,或者執行失敗的丟擲的異常,在我們定義的業務邏輯(ThreadPoolExecutor#sumbit傳入的Callable,或者ThreadPoolExecutor#execute傳入的Runnable)成功執行結束,到執行的結果寫回到outcome,並不是一個瞬間動作,在執行結果賦值到outcome的時候,會先將FutureTask狀態修改為COMPLETING

這樣做的目的是,任務處於COMPLETING,這是一個執行緒呼叫get獲取任務的時候,不會讓呼叫執行緒阻塞而是讓這個執行緒yield放棄cpu稍等片刻,任務結果馬上寫回了。

1.2什麼是INTERRUPTING,正在中斷是什麼鬼

考慮一個情況,執行緒A正在執行任務,執行緒BCD都呼叫了get,執行緒E呼叫了FutureTask#cancel(true),這時候需要中斷執行緒A,並且我們在這個FutureTask的業務邏輯中定義了,如果被中斷,那麼任務結束,丟擲異常等邏輯

這時候FutureTask#run會丟擲一個異常(什麼異常取決於FutureTask的業務邏輯丟擲什麼),那麼執行緒BCD呼叫get造成的阻塞如何處理,需要去喚醒BCD,在修改狀態到中斷其實是存在短暫的時間的,在這短暫的時間內執行緒A會yield,放棄CPU,等待執行緒E中斷方法呼叫結束,然後執行緒E會喚醒BCD。

2.任務,執行任務的執行緒,任務執行結果

image-20230102222555133

任務被包裝為Callable物件,任務結果使用outcome屬性記錄,執行任務的執行緒使用runner屬性記錄。

需要注意的是這裡的outcome,沒有使用volatile,後面的註釋寫到non-volatile, protected by state reads/writes(不使用volatile修飾,可見性由state屬性的寫入和讀取保證),為什麼不需要volatile關鍵字修飾,我們看原始碼的時候解釋

3.WaitNode ——等待任務執行結束的執行緒

我們上面說到,呼叫get方法的時候,如果任務沒有結束,那麼呼叫執行緒,將阻塞到任務結束。這意味著任務結束的時候,呼叫執行緒將被喚醒,那麼哪些執行緒需要喚醒?FutureTask使用WaitNode型別的屬性記錄

image-20230102223036435

透過WaitNode屬性記錄呼叫執行緒,並且使用next屬性串聯起其他等待的執行緒,使用呼叫執行緒的記錄。

image-20230102223650870

看完這些屬性,我們來拜讀doug lea大師的程式碼

六丶原始碼解讀

1.構造方法

image-20230102224012912

可以看到如果傳入Runnable,那麼將被使用RunnableAdapter包裝成Callable,典型的介面卡模式,透過RunnableAdapter適配RunnableCallable

需要注意callable沒有使用volatile修飾,doug lea 不擔心重排序的問題麼

如果執行FutureTask的構造方法的時候,發生重排序,this.callable的賦值重排序到外部獲取到構造方法生成的FutureTask的後面,並且立馬有另外一個執行緒呼叫了FutureTask的任務執行方法,這時候this.callable還來不及賦值,呼叫執行方法丟擲空指標異常。那麼為什麼不用volatile修飾callable還能保證其可見性暱,能讓原始碼寫上// ensure visibility of callable這行註釋暱?

在《JUC原始碼學習筆記4——原子類,CAS,Volatile記憶體屏障,快取偽共享與UnSafe相關方法》的學習筆記中,我們說過volatile變數寫具備如下記憶體屏障

img

這裡的store store屏障防止了this.callable的賦值重排序到this.state = NEW之後,且後續的store屏障會保證當前執行緒(構造FutureTask的執行緒)工作記憶體會立馬寫回到主記憶體,並讓其他執行緒關於此FutureTask的快取無效,從而保證了callable的執行緒可見性。

2.run

我們從run方法看起,run方法中的許多邏輯,牽扯到其他的方法,可能需要總覽全域性才能徹底理解

public void run() {
    //如果不是初始,說明有其他執行緒啟動了,或者說其他執行緒取消了任務 那麼不需要執行
    //如果是new 但是cas runner失敗了,說明同時有多個執行緒執行此cas,當前執行緒沒有搶過,那麼不能執行此任務,
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
        Callable<V> c = callable;
	
        //再次校驗下是否為初始狀態 如果不是 說明在當前線從第一個if到此存在其他執行緒取消任務
        //任務啟動之前可以取消任務的執行
        if (c != null && state == NEW) {
            V result;
	        //記錄當前任務是否成功執行,如果Callable程式碼寫錯了,
            //或者說Callable響應中斷,執行的途中被中斷那麼為false
            boolean ran;
            try {
                //業務邏輯執行
                result = c.call();
                //成功執行
                ran = true;
            } catch (Throwable ex) {
                //這裡可能是Callable本身程式碼邏輯錯誤異常 也可能是響應中斷丟擲異常
                result = null;
                ran = false;
           		//記錄異常
                setException(ex);
            }
            if (ran)
                //設定任務正常執行結果
                set(result);
        }
    } finally {
        runner = null;
        int s = state;
        //處理中斷
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}
  • if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))

    任務不是new,或者cas設定當前執行緒為runner失敗,那麼直接返回false

    • 如果任務當前狀態不是new,說明有其他執行緒執行了,或者說其他執行緒取消了任務
    • 如果是new 但是cas runner失敗了,說明同時有多個執行緒執行此cas,當前執行緒沒有搶過,那麼不能執行此任務,這裡使用CAS確保任務不會被其他執行緒再次執行
  • if (c != null && state == NEW) 為什麼上面state!=NEW都false了還要再次判斷這裡state==NEW

    因為state != NEWUNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) 並非是一個原子性操作,這裡是為了確保前執行緒從第一個if到第二個if,這一段程式碼執行時間內,沒有其他執行緒取消任務。如果存在其他執行緒取消了任務,那麼state == NEW就不成立——任務執行前可以取消任務

2.1記錄任務執行結果setExceptionset方法

  • 如果執行出現異常

    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }
    
  • 正常執行結束

    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }
    

這兩個方法都差不多,都是上來一個CAS將state從new轉變為COMPLETING,然後用outcome記錄異常或者記錄成功返回值,然後使用UNSAFE.putOrderedInt改變state,如果是出現異常,那麼設定狀態為EXCEPTIONAL,如果正常結束設定為NORMAL

  • 為什麼使用UNSAFE.putOrderedInt 為什麼outcome沒有使用volatile修飾,doug lea都不擔心可見性的問題麼

    UNSAFE.putOrderedInt這個方法我在《JUC原始碼學習筆記4——原子類,CAS,Volatile記憶體屏障,快取偽共享與UnSafe相關方法》中關於AtomicInteger lazySet中說過,Store load屏障可以讓後續的load指令對其他處理器可見,但是需要將其他處理器的快取設定成無效讓它們重新從主記憶體讀取,putOrderedInt提供一個store store屏障,然後寫資料,store store是保證putOrderedInt之前的普通寫入和putOrderedInt的寫入不會重排序,但是不保證下面的volatile讀寫不被重排序,省去了store load記憶體屏障,提高了效能,但是後續的讀可能存在可見性的問題。putOrderedInt的store store屏障保證了outcome回立即重新整理回主存

    //也就是說
    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    		//outcome是重新整理回主存的,且不會重排序到putOrderedInt後面
            //這也是outcome沒有使用volatile修飾的原因之一,
            //有後續呼叫putOrderedInt方法保證其對其他執行緒的可見性
            outcome = v;
            
            //state欄位使用putOrderedInt寫入 其他執行緒存在可見性的問題
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            
            finishCompletion();
        }
    }
    

    那麼state的可見性問題doug lea 如何解決?接著看下去

2.2finishCompletion 完成對等待執行緒的喚醒

private void finishCompletion() {
    // 等待的節點
    for (WaitNode q; (q = waiters) != null;) {
        
        //將當前futureTask的waiters屬性cas為null
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            //喚醒所有get方法阻塞的執行緒
            for (;;) {
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    //喚醒
                    LockSupport.unpark(t);
                }
                WaitNode next = q.next;
                //直到一個為null的節點,意味著遍歷結束
                if (next == null)
                    break;
                q.next = null; // unlink to help gc
                q = next;
            }
            //結束
            break;
        }
    }
	//鉤子方法 留給我們自己擴充套件
    done();
	
    //將任務置為null
    callable = null;        // to reduce footprint
}

這裡拿到waiters然後進行自旋遍歷所有等待的節點執行緒,然後喚醒它們,有意思的點在UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)為何這裡要使用CAS更新waiters為null暱?

因為這裡存線上程A執行完FutureTask呼叫finishCompletion的同時執行緒B呼叫get進行等待,呼叫get方法進行排隊(排隊時也是CAS設定自己為waiters)這兩個CAS必定有一個成功,有一個失敗

  • 如果A失敗,說明B在A喚醒之前進行排隊,掛起自己,那麼A在自旋喚醒的時候會喚醒B
  • 如果B失敗,那麼說明B在A喚醒之後進行排隊,那麼這時候不需要排隊了,因為任務已經完成了,B只需要進行自旋獲取返回結果即可

這一點我們在get方法的原始碼分析的時候,會深有體會

其次在取消任務的時候也會呼叫finishCompletion喚醒等待的執行緒,所有finishCompletion的呼叫存線上程安全問題,需要使用cas保證執行緒安全

2.3處理因為cancel造成的中斷#handlePossibleCancellationInterrupt

在run方法的finally塊中存在

//執行完設定runner為空
runner = null;
//重新獲取狀態
int s = state;
 //如果是INTERRUPTING 或者INTERRUPTED
if (s >= INTERRUPTING)
    //handlePossibleCancellationInterrupt
    handlePossibleCancellationInterrupt(s);

private void handlePossibleCancellationInterrupt(int s) {
    if (s == INTERRUPTING)
        //如果是打斷中 那麼等待直到結束打斷
        while (state == INTERRUPTING)
            Thread.yield(); 

cancel方法可以選擇傳入true表示,如果任務還在執行那麼呼叫執行任務執行緒的interrupt方法進行中斷,如果是呼叫cancel的執行緒還沒有完成中斷那麼當前執行的執行緒會讓步,為什麼這麼做,我們上面說到過,A執行緒執行任務,B執行緒cancel任務,B中斷執行緒A其實是需要時間的,B會先修改任務狀態為INTERRUPTING,然後中斷執行緒A,然後修改狀態為INTERRUPTED並喚醒等待的執行緒,從INTERRUPTING - > INTERRUPTED 這段時間,執行緒A只需要讓出cpu等待即可

3.get 獲取任務執行結果

  • get()無限等待任務執行完
public V get() throws InterruptedException, ExecutionException {
    int s = state;
    //任務為NEW 和 COMPLETING 那麼呼叫那麼會呼叫awaitDone
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    //此方法如果發現FuturetTask呼叫異常那麼丟擲異常
    return report(s);
}
  • get(long timeout, TimeUnit unit)超時等待任務完成
public V get(long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {
    if (unit == null)
        throw new NullPointerException();
    int s = state;
    //如果狀態小於等於COMPLETING ( NEW 和 COMPLETING) 那麼會呼叫awaitDone
    //如果awaitDone結束的時候返回的狀態還是 NEW or COMPLETING 丟擲超時異常
    if (s <= COMPLETING &&
        (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
        throw new TimeoutException();
    //此方法如果發現FuturetTask呼叫異常那麼丟擲異常
    return report(s);
}

二者最終都呼叫了awaitDone(是否超時等待,等待時長)

3.1如果狀態小於等於COMPLETING

這意味著 狀態為new,任務都執行完業務邏輯,或者狀態為COMPLETING,業務邏輯執行完了但是outcome正在賦值為執行結果,對outcome賦值後,會修改狀態為NORMAL(任務正常完成),或者EXCEPTIONAL(任務執行丟擲異常),所有get方法只有狀態小於等於COMPLETING,才會呼叫awaitDone掛起執行緒進行等待

3.2 awaitDone等待直到任務完成或者超時

呼叫此方法的前提是,任務邏輯沒有執行完,或者邏輯執行完但是結果還沒有賦值給outcome,那麼這兩種情況doug lea如何處理

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    //等待結束時間,如果非超時等待,那麼為0
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
	//當前執行緒如果進入等待任務完成佇列,此變數記錄等待節點
    WaitNode q = null;
    //是否入隊(等待任務完成佇列)Waiters 使用next組成的佇列
    boolean queued = false;
    for (;;) {
        //如果等待的過程中被中斷,
        //那麼把自己從等待waiters中刪除
        //並且丟擲中斷異常
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        //讀取state,volatile保證可見性
        int s = state;
        //如果當前大於COMPLETING 說明任務執行完成 outcome已經賦值了,
        //或者取消了,或者由於取消而被中斷 直接返回當前狀態,不需要再等了
        if (s > COMPLETING) {
            //節點執行緒置為null,後續執行任務執行緒喚醒等待執行緒的時候不會喚醒到此執行緒
            if (q != null)
                q.thread = null;
            return s;
        }
        //如果任務正在完成,進行執行緒讓步
        //後續FutureTask執行的執行緒寫回outcome改變狀態為NORMAL或者EXCEPTIONAL是很快的,
        //也許修改狀態為NORMAL或者EXCEPTIONAL會導致執行緒A多yield幾下(使用的是UNSAFE.putOrderedInt存線上程可見性問題)
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        //當前執行緒的節點
        else if (q == null)
            q = new WaitNode();
        //如果沒有入隊(等待任務完成的佇列)那麼入隊(等待任務完成的佇列)
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        //如果是超時等待
        else if (timed) {
            nanos = deadline - System.nanoTime();
			//等待超時 把自己從等待任務完成佇列中移除
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            //等待指定時間
            LockSupport.parkNanos(this, nanos);
        }
        else
            //無限期等待
            LockSupport.park(this);
    }
}

此方法分支很多,我們慢慢看

  1. 如果執行緒執行awaitDone之前就被中斷,那麼會直接丟擲中斷異常

  2. 如果執行緒執行awaitDone並且成功使用LockSupport.parkNanos(this, nanos)或者LockSupport.park(this)掛起,這時候被中斷會從這兩個方法中返回,繼續自旋,Thread.interrupted()為true,會重置中斷標識並且丟擲中斷異常

    1和2其實都是FutureTask#get對於中斷的響應,get方法如果被中斷是會丟擲中斷異常並且重置中斷標識的

  3. 如果自旋的時候發現任務狀態大於COMPLETING

    說明當前任務執行完成了,或者說任務被取消,或者由於取消已經中斷了,那麼直接返回即可,從這裡返回有三種情況第一次自旋發現任務完成了超時等待指定時間結束發現任務完成了,任務完成的時候被任務執行執行緒喚醒,繼續自旋發現任務完成了

  4. 如果自旋的時候發現任務狀態等於COMPLETING

    那麼呼叫yield讓出cpu,而不是掛起自己,因為後續FutureTask執行的執行緒寫回outcome改變狀態為NORMAL或者EXCEPTIONAL是很快的,也許修改狀態為NORMAL或者EXCEPTIONAL會導致執行緒A多yield幾下(使用的是UNSAFE.putOrderedInt存線上程可見性問題)(這裡就是我們說的為什麼COMPLETING不擔心可見性問題)

    注意如果超時等待指定時間結束,繼續自旋,如果進入此分支,那麼讓出cpu,再次獲得時間片後,繼續執行,下一次自旋,而不會進入到下面的超時異常分支,也就是說COMPLETING意味著任務執行完了,但是在做一些善後工作(寫入任務返回值,喚醒等待執行緒)不會由於此狀態導致超時

  5. q == null

    意味著當前執行緒沒有被包裝成WaitNode,當前執行緒也沒有被中斷,任務沒有完成也不是Completing狀態,這時候呼叫構造方法然後繼續自旋

    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }
    
  6. !queued

    這是在5的基礎上,當前q已經有了節點,但是還沒有進入等待任務完成佇列,下面透過CAS讓當前執行緒入隊

    else if (!queued)
        queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                             q.next = waiters, q);
    

    這裡首先q.next = waiters讓當前節點的next指向waiters,然後CAS設定waiters為當前節點,也就是說最後入隊的節點使用waiters記錄,使用next串聯每一個等待的執行緒節點,q.next = waiters不需要考慮執行緒安全,和AQS中的入隊類似,這是改變當前節點的next引用指向,但是修改waiters需要考慮執行緒安全問題,如果這裡CAS失敗了(意味著存在其他執行緒呼叫get入隊等待),那麼queued為false 繼續自旋嘗試CAS自己為waiters

  7. 掛起當前執行緒

    //超時等待
    else if (timed) {
        //需要等待的時間
        nanos = deadline - System.nanoTime();
        //已經超時
        if (nanos <= 0L) {
            //把自己從waiters等待任務完成佇列中移除
            removeWaiter(q);
            return state;
        }
        //掛起指定時間
        LockSupport.parkNanos(this, nanos);
    }
    //等待直到被中斷或者喚醒
    else
        LockSupport.park(this);
    

    這裡超時部分多一個removeWaiter,將自己從等待任務完成佇列中移除,這個方法的執行需要考慮執行緒安全問題,同樣使用自旋+CAS保證執行緒安全,這裡不做過多分析。

4 report 如果任務執行失敗丟擲異常,如果成功返回執行結果

private V report(int s) throws ExecutionException {
    Object x = outcome;
    //如果任務正常結束
    if (s == NORMAL)
        //強轉
        return (V)x;
    //如果任務取消了 或者由於取消被中斷了,丟擲取消異常
    if (s >= CANCELLED)
        throw new CancellationException();
    //反之丟擲ExecutionException 包裝 原始的異常
    throw new ExecutionException((Throwable)x);
}

只有任務正常執行的時候,才會返回結果,如果被取消那麼丟擲取消異常。

5 取消任務

取消有一個比較有趣的點,如果取消在任務開始之前,那麼說明取消成功,後續任務完成呼叫set或者setException應該是什麼都不做。如果取消在任務執行之後,那麼取消的這個動作應該失敗,下面我們看下doug lea如果處理這個細節。

//mayInterruptIfRunning 表示需要中斷任務執行執行緒
public boolean cancel(boolean mayInterruptIfRunning) {
    //任務不是初始,或者CAS修改狀態從new 到INTERRUPTING 或者CANCELLED 失敗
    //直接返回false
    if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    try {   
        //如果需要中斷
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                //執行中斷
                if (t != null)
                    t.interrupt();
            } finally { // final state
                //修改狀態為INTERRUPTED
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        //喚醒所有等待任務執行的執行緒
        finishCompletion();
    }
    return true;
}
  1. 第一個if

    if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    
    • 如果 state == NEW不成立,說明任務在執行此判斷之前已經結束了(Completing,或者已經到了NORMAL,或者EXCEPTIONAL)說明取消在任務結束之前,那麼直接返回false。或者說當前執行緒A對任務的取消在其他執行緒B取消任務之後,這時候就A執行緒取消方法返回false

    • 如果UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))不成立

      意味著state == NEW判讀的時候成立,但是執行這句CAS的時候之前有老六執行緒搶先一步,或者說存在併發當前執行緒沒有搶過,那麼也直接返回false,這裡保證了cancel的執行是序列的,不存線上程安全問題。注意這裡如果需要中斷任務執行執行緒那麼CAS修改狀態到INTERRUPTING ,反之直接修改到CANCELLED

  2. 嘗試中斷任務執行執行緒

    if (mayInterruptIfRunning) {
        try {
            Thread t = runner;
            if (t != null)
                t.interrupt();
        } finally { // final state
            UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
        }
    }
    

    呼叫interrupt具體如何響應中斷,得看程式設計師定義的業務邏輯是啥樣的。呼叫putOrderedInt修改狀態為INTERRUPTED,表示已經完成了中斷

  3. 喚醒等待任務結束的執行緒

    直接呼叫finishCompletion,這個方法前面分析過。這裡假如執行緒A取消了任務,那麼執行緒B任務執行完後呼叫set或者setException 會如何暱——什麼都不做

    protected void set(V v) {
        //此時state 是INTERRUPTING 或者INTERRUPTED if為false
     if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
                outcome = v;
                UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
                finishCompletion();
            }
        }
    
  4. run方法對中斷的處理

    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            //省略任務的執行
        } finally {
            runner = null;
            int s = state;
            //進入這個if 必須是INTERRUPTING,或者INTERRUPTED
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    
    private void handlePossibleCancellationInterrupt(int s) {
        if (s == INTERRUPTING)
            while (state == INTERRUPTING)
                Thread.yield(); 
    }
    

這裡為INTERRUPTING ,可能是取消任務的執行緒還沒來得及執行UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED),也可能是可見性導致執行任務的執行緒沒有讀取到最新的state,handlePossibleCancellationInterrupt會讓執行任務的執行緒等待

七丶問題解答

  • get方法呼叫的時候,如果任務沒有成功,怎麼實現呼叫執行緒的阻塞

    等待的執行緒修改Waiter next指向 CAS自己為waiters,執行緒安全的入隊,並進行park 掛起實現阻塞

  • get方法出現異常,或者正常結束的時候,怎麼喚醒呼叫get方法的執行緒

    在finishCompletion中進行喚醒,如果任務沒有被取消,執行完,那麼執行任務的執行緒進行喚醒,如果任務被取消那麼cancel任務的執行緒進行喚醒

  • 在任務執行之前,如果任務被取消那麼將無法執行,已經開始執行的任務只能嘗試中斷執行任務的執行緒,無法取消,那麼doug lea 怎麼判斷執行和取消的先後順序(注意執行和取消可以是多個執行緒呼叫)怎麼處理這裡的執行緒不安全問題

    使用CAS,執行前保證為new,取消的時候也要保證為new,且執行完成修改new使用cas,取消也是使用cas,保證執行緒安全

相關文章