Executor多線程框架學習筆記(五):FutureTask

FutureTask

ScheduledThreadPoolExecutor裏面用到了FutureTask,提前將對其進行講述

public class FutureTask<V> implements RunnableFuture<V> {
    //當前任務的運行狀態 volatile修飾了他的可見性
    //因爲任務在運行中是使用子線程運行的在其他線程中可以對任務進行取消和中斷等操作,所以這裏修飾爲了再多線程中顯示
    private volatile int state;
    //以下是state的幾個值
    //任務初始是new
    private static final int NEW          = 0;
    //任務執行完成
    private static final int COMPLETING   = 1;
    //任務正常
    private static final int NORMAL       = 2;
    //任務執行時發生異常
    private static final int EXCEPTIONAL  = 3;
    //任務取消
    private static final int CANCELLED    = 4;
    //任務中斷中
    private static final int INTERRUPTING = 5;
    //任務中斷
    private static final int INTERRUPTED  = 6;
    //下面是任務狀態的轉換順序
    /*
    NEW -> COMPLETING -> NORMAL
    NEW -> COMPLETING -> EXCEPTIONAL
    NEW -> CANCELLED
    NEW -> INTERRUPTING -> INTERRUPTED
    */
    //最終執行的回調方法此接口內部只有一個call方法並且可以獲取執行的返回值
    private Callable<V> callable;
    //任務執行的返回值,也就是callable執行的結果
    //此結果並沒有可見性修飾是因爲對他的操作都是原子操作
    private Object outcome; 
    //此線程用於運行callable
    private volatile Thread runner;
    //等待任務執行結果節點
    private volatile WaitNode waiters;
    //獲取最終的執行結果,可以看出是private修飾的是它內部使用的方法
    @SuppressWarnings("unchecked")
    private V report(int s) throws ExecutionException {
        //獲取callable的返回結果
        Object x = outcome;
        //如果傳入的狀態是正常結束則返回獲取的結果
        if (s == NORMAL)
            return (V)x;
        //如果獲取的結果是取消或者中斷則拋出取消異常
        if (s >= CANCELLED)
            throw new CancellationException();
        //可以通過上方的結果進行排除還有異常狀態、初始狀態、執行完成狀態
        //但是這裏只處理了異常狀態並且包裝成ExecutionException
        //因爲其與兩個狀態並不會進入此方法,在下面會講述
        throw new ExecutionException((Throwable)x);
    }

    //構造函數傳入的Callable的實現對象
    //並且初始化狀態爲new
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // 這裏操作state將會叫所有操作此對象的線程得知state狀態初始化爲了new,也是volatile修飾的原因
    }
    //第二個構造器,支持傳入runnable實現,但是任務執行的試callable所以需要第二個參數傳入runnable的返回值
    public FutureTask(Runnable runnable, V result) {
        //通過傳入的兩個參數調用了Excetors用來創建callable實現
        //返回時RunnableAdapter的實例,此對象非常簡單就是調用runnable的run方法並且返回傳入result
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;
    }
    //判斷任務是否取消,中斷也是取消所以此處大於等於取消
    public boolean isCancelled() {
        return state >= CANCELLED;
    }
    //判斷是否完成,只要不是NEW都算完成,所以此方法判斷是否執行完畢得到的結果是錯誤的。
    //因爲運行中狀態並不能算是完成因爲並沒有確切的結果
    public boolean isDone() {
        return state != NEW;
    }
    //取消執行任務
    //mayInterruptIfRunning 參數比較有含義,具體含義需要根據代碼講解
    public boolean cancel(boolean mayInterruptIfRunning) {
        //進入此方法先判斷state==new 當前狀態是new並且對state進行原子操作。
        //UNSAFE.compareAndSwapInt(this, stateOffset, NEW,mayInterruptIfRunning ? INTERRUPTING : CANCELLED)
        //傳入當前對象並且傳入state在當前對象中的內存偏移傳入預計的值代表我認爲當前state值是new,然後判斷mayInterruptIfRunning如果爲true則代表終端中,否則是取消狀態
        //在最外層有個取反動作,代表如果當前任務狀態是new並且成功設置爲了中斷中或者取消狀態則執行下面的語句進行後續處理,如果當前狀態已經不是new則返回false代表取消失敗
        //這裏有點繞,可以理解爲如果當前狀態是NEW並沒有運行則代表取消失敗如果當前狀態是其他狀態則會繼續向下運行。
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    
            //進入此處需要注意的是當前任務已經在運行中
            //而mayInterruptIfRunning變量繼續發揮作用如果是true
            //上面代碼也看到是true則設置爲中斷中狀態所以此處獲取當前任務的執行線程並且調用中斷
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    //最終結果一定是一個具體的結果,而上方設置爲了中斷中,所以在線程調用中斷後再次設置狀態爲中斷
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            //當中斷操作完成則進行完成操作
            finishCompletion();
        }
        // 這裏做個小結: 此方法取消狀態爲false 則代表此方法並未運行,爲true則代表此方法運行中已經取消,而mayInterruptIfRunning參數則代表是立即中斷線程取消,還是等待線程正常執行完,不管哪一種最終的狀態都是取消
        //到此處則代表取消成功
        return true;
    }
    //獲取任務執行結果
    //拋出了兩個異常 線程中斷異常,肯定是調用了上方的取消或者線程被意外幹掉,執行異常,是FutureTask的結果異常代表在任務運行期間發生了異常,之前在將report方法的時候又看到拋出了CancellationException異常,這裏並沒有拋出,因爲此異常是運行時異常
    public V get() throws InterruptedException, ExecutionException {
        //獲取當前任務運行狀態
        int s = state;
        //如果任務是未運行NEW或者執行完成COMPLETING狀態則
        if (s <= COMPLETING)
            //當前線程等待完成傳入參數後面講解等待的時候會進行說明
            s = awaitDone(false, 0L);
        //不管是否執行完最終都會得到結果,如果執行完則直接返回結果
        //如果未執行完則等待執行直到結束所以s永遠都會有值
        //而此方法也可以叫做死等待,他會一直等待直到結束所以此方法阻塞
        return report(s);
    }
    //此方法是上方方法的重構,傳入兩個參數1、超時時間類型long2、傳入這串時間的時間類型是秒還是毫秒或者說納秒
    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        //不管傳入的單位是什麼最終他會以納秒進行判斷
        //如果當前狀態是未運行或者運行中狀態則等待與方法調用不同的是傳入了true,並且傳入了超時的納秒數,如果等到超時則拋出超時異常
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }
    //丟給子類的鉤子在任務完成的時候進行調用的
    protected void done() { }
    //此處是設置具體的執行結果
    protected void set(V v) {
        //之前說過執行結果是原子操作,所以此處使用了原子操作進行對結果操作
        //但是並不是直接原子操作操作執行結果而且使用了狀態值
        //如果當前狀態是NEW則修改爲執行完成,修改失敗則代表此任務已經被別的線程干預了取消或者中斷
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            //到此處說明並沒有其他線程干預結果從而設置當前值爲傳入的v
            outcome = v;
            //此處設置當前狀態爲正常,並且防止執行爲重排序
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL);//當到了最終狀態則調用最後的處理方法
            finishCompletion();
        }
    }  
    //設置當前任務結果爲異常,操作與上方設置值一樣只不過最終設置結果不是正常而是異常
    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }
    //任務的執行方法
    public void run() {
        //如果當前任務不是NEW代表已經有人在執行了所以直接return
        //但是隻根據這個狀態判斷是不準確的再具體完成時一直是NEW所以在此設置當前的運行線程爲當前線程先判斷當前任務的運行線程是否爲null如果是則設置爲當前線程,如果設置失敗則代表runner已經有值了所以也是reutrn
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            //設置成功則獲取當前的callabel
            Callable<V> c = callable;
            //如果當前任務不是null並且當前狀態是NEW
            //這是爲了防止在這一段代碼執行時其他線程進行取消操作
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    //因爲進入這裏的都是執行線程了所以直接調用call方法
                    //這裏可以看到使用了try捕獲執行異常如果正常則設置ran爲true
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    //如果發生了異常則設置當前值爲執行一次並且設置ran爲false
                    result = null;
                    ran = false;
                    setException(ex);
                }
                //如果ran則代表正常執行完成則設置獲取到的結果
                if (ran)
                    set(result);
            }
        } finally {
            //到此處runner設置爲null,runner最終的用處就是確保run方法在併發下只執行一次,而之所以在這設置爲null因爲結果和最終狀態已經確定了
            runner = null;
            //獲取當前狀態,如果在運行到此處被中斷或者去取消則進行之後的處理
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    //此方法與run方法最大的區別在於run方法只執行一次,而此方法會將當前任務重置可以再次執行,用於週期任務的執行
    protected boolean runAndReset() {
        //同樣使用當前狀態和當前執行線程用來鎖住方法在同一次執行只執行一次,因爲可能多個線程同時執行此方法
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return false;
        //獲取執行結果和當前任務的狀態
        boolean ran = false;
        int s = state;
        try {
            //獲取當前需要執行的具體任務callable
            Callable<V> c = callable;
            //任務不是null並且當前狀態是NEW則調用call方法並且設置允運行結果狀態爲true,運行失敗則設置結果內容爲執行異常
            if (c != null && s == NEW) {
                try {
                    c.call(); // don't set result
                    ran = true;
                } catch (Throwable ex) {
                    setException(ex);
                }
            }
        } finally {
            //與run方法一致
            runner = null;
            s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
        //因爲此處沒有設置結果爲正常 所以當正常執行完成狀態應該還是NEW
        //如果不是NEW則代表運行時發生可異常從而運行失敗返回false
        return ran && s == NEW;
    }
    //所謂的處理中斷就是等待取消方法的中斷,因爲在講取消方法的時候先會將當前狀態設置爲INTERRUPTING,然後進過一系列處理再將它設置爲INTERRUPTED(這裏是指mayInterruptIfRunning爲true如果爲false則此方法不會有執行操作因爲只有s爲INTERRUPTING的時候纔會進入while)
    //而由於cpu執行速度非常快並不一定執行到這裏會將cancel方法執行完。
    private void handlePossibleCancellationInterrupt(int s) {
        //所以此處判斷當前狀態如果是INTERRUPTING則告訴cpu我沒有什麼可以執行了放棄當前佔用的cpu資源,如果cpu忽略你的放棄或者並沒有其他資源用到cpu從而會是死循環不聽的說我放棄cpu資源
        //直到狀態修改爲最終的INTERRUPTED此方法結束
        if (s == INTERRUPTING)
            while (state == INTERRUPTING)
                Thread.yield(); 
    }
    //等待節點,在獲取結果數據的時候可能會有很多線程等待獲取從而不確定數量所以java採用了鏈表作爲等待返回的喚醒,在講解創建的時候將會詳細介紹
    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }

    //之前在設置結果的時候不管是正常結束還是異常結束設置結果或者是    取消都使用到了此方法
    //而此方法的目的就是釋放等待結果的線程,在get中有awateDone方法用於當前獲取結果的線程等待執行結果,而此方法就是對線程的喚醒
    private void finishCompletion() {
        // assert state > COMPLETING;
        //聲明q臨時變量,並且將當前的等待鏈表賦值給他進行for遍歷條件是q不等null等於null說明鏈表遍歷完畢
        for (WaitNode q; (q = waiters) != null;) {
            //賦值過後緊接着給當前的等待列表賦值爲null,可以看出採用了cas比較並且交換如果在賦值後等待列表發生了改變那麼將會使此處比較失敗從而不進if繼續for循環進行復制操作。
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                //如果比較成功並且設置爲null則代表再次之間等待鏈表並沒有發生改變,從而進入這個死循環
                for (;;) {
                    //獲取當前節點的線程
                    Thread t = q.thread;
                    if (t != null) {
                        //不等於null則喚醒當前線程
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    //然後獲取當前節點的next
                    WaitNode next = q.next;
                    //如果等於null則跳出死循環
                    if (next == null)
                        break;
                    //並且將q的next賦值爲null
                    //此處是爲了方便GC回收從而斷開q.next的引用
                    q.next = null; 
                    //將next節點賦值給q
                    //直到next==null的時候跳出循環,再次查看當前的等待鏈表是否又有新的等待線程如果有則繼續重複否則此方法結束
                    q = next;
                }
                break;
            }
        }
        //調用完成方法,此方法在這裏是空的如果有特殊需要實現子類繼承即可
        done();
        //將當前的任務設置爲null,以便減少對內存的引用這樣方便回收
        callable = null; 
    }
    //此方法和上方方法對應,此方法是用來給鏈表裏添加等待線程的
    //如果有線程在任務未執行完的時候調用了get方法將會進入此方法等待
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        //獲取他的死線值,死線值意思是死等結果的時間
        //如果設置了等待時間則timed是true則用當前時間的納秒值加上傳入的納秒值獲取等待的最終時間如果是timed是false則返回0L
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        //創建一個鏈表節點
        WaitNode q = null;
        //是否已經加入到當前隊列
        boolean queued = false;
        //進入死循環進行操作
        for (;;) {
            //如果當前線程已經被中斷
            if (Thread.interrupted()) {
                //則從等待列表中移除並且拋出異常
                removeWaiter(q);
                throw new InterruptedException();
            }
            //獲取當前狀態
            int s = state;
            //如果大於COMPLETING運行完成狀態則代表已經有具體的結果值了從而清空當前節點中的線程引用方便GC回收
            if (s > COMPLETING) {
                if (q != null)
                    q.thread = null;
                return s;
            }
            //如果當前狀態是運行完成則告訴系統當前cpu資源可以不用了,因爲下面執行都是對線程等待的操作,既然已經知道結果了所以就跳過等待的操作從而進入上一個if直接返回結果
            else if (s == COMPLETING) // cannot time out yet
                Thread.yield();
            //走到這裏狀態肯定是NEW等待執行或者執行中從而創建一個節點new 此節點的構造自動賦值thread爲當前線程
            else if (q == null)
                q = new WaitNode();
            //到此處則判斷是否加入隊列而這裏的queued代表是否已經加入隊列
            //false代表併爲加入所以取反進入if
            else if (!queued)
                //因爲說是鏈表所以他的next屬性指向原先的列表的頭從而成爲新的頭部
                //然後比較當前運行時間點的等待列表是否進行了更新如果更新則繼續循環判斷上方的if否則將當前的等待列表進行更新
                queued = UNSAFE.compareAndSwapObject(
                    this, waitersOffset,q.next = waiters, q);
            //如果到此說明已經加入到了等待列表並且當前等待是有設置超時的
            else if (timed) {
                //從而判斷在循環中循環的時間是否達到了用戶設置的超時時間
                nanos = deadline - System.nanoTime();
                //如果達到則是小於等於0 
                if (nanos <= 0L) {
                    //從而刪除節點並且返回當前的狀態,因爲等待時間已經超過用戶的設置時間
                    removeWaiter(q);
                    return state;
                }
                //否則則用互斥鎖將當前的線程進行阻塞並且限制時間
                LockSupport.parkNanos(this, nanos);
            }
            else
                //如果沒有設置時間則永久等待直到喚醒
                LockSupport.park(this);
            //這裏要注意park、parkNanos兩個方法是阻塞代表如果喚醒將還會在此處代碼,說明喚醒後還在此處死循環這樣再次經過上方的判斷從而得到結果,當然寫入等待節點只會執行一次
        }
    }
    //刪除等待節點
    private void removeWaiter(WaitNode node) {
        //當前傳入節點不是null
        if (node != null) {
            //設置當前節點的線程引用null
            node.thread = null;
            //設置死循環的別名叫retry
            retry:
            for (;;) {
                //聲明三個變量pred q s,這三個變量都是WaitNode類型
                //pred 代表頭node
                //q 當前鏈表頭
                //s 當前鏈表頭的next
                //條件是q != null
                //而操作是q = s代表每次循環q都已經修改爲q.next
                for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
                    //給s賦值當前的鏈表頭
                    s = q.next;
                    //如果q的線程不是null則將q設置爲鏈表頭
                    //如果等於null則代表此節點需要刪除因爲在進入刪除節點方法的時候就設置節點線程值爲null了
                    if (q.thread != null)
                        pred = q;
                    //如果頭節點不是null則代表刪除節點已經不是頭節點了所以講頭結點的next設置爲之前鏈表頭的next節點
                    else if (pred != null) {
                        pred.next = s;
                        //進行檢查是否當前節點被移除 如果是則重新進入死循環
                        if (pred.thread == null) // check for race
                            continue retry;
                    }
                    //如果第一個節點就是q則通過cas進行移除第一個節點,否則通過上方邏輯進行移除
                    else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                          q, s))
                        continue retry;
                }
                break;
            }
            //此方法運行案例
            //鏈表值[1,2,3,4,5]
            //如果要移除3
            //第一次循環值爲 pred = 1 q = 1 s = 2
            //第二次循環值爲 pred = 2 q = 2 s = 3
            //第三次循環值爲 pred = 3 q = 2 q.next = 4 此處鏈表結果爲:[1,2,4,5]
            //第四次循環值爲 pred = 4 q = 4 s = 5
            //第五次循環置爲 pred = 5 q = 5 s = null從而跳出循環進入外層死循環
            //如果要移除1
            //第一次循環   pred = null q = 1 s = 2 通過cas替換當前等待鏈表的頭從而結果爲[2,3,4,5]
        }
    }

    // 文章中有很多cas操作而cas操作需要使用到內存中的地址偏移值而此處則是通過靜態代碼塊獲取三個屬性的偏移值一遍操作的時候使用。
    //因爲類的成員偏移是不會變的不管你是否賦值都會有哪一段空間所以此處使用靜態獲取並且操作的時候傳入了this進行了優化否則個對象都需要獲取偏移。
    private static final sun.misc.Unsafe UNSAFE;
    private static final long stateOffset;
    private static final long runnerOffset;
    private static final long waitersOffset;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = FutureTask.class;
            stateOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("state"));
            runnerOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("runner"));
            waitersOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("waiters"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章