ForkJoinPool源碼深度解析

ForkJoinPool的整體邏輯其實相對於AQS來說簡單多了, 但是它的實現裏面用了很多二進制的邏輯運算,導致整個實現看起來非常難,所以在正式的看ForkJoinPool的代碼之前,先看一下二進制的一些玩法,這些玩法是我在ForkJoinPool的代碼中摘出來的,明白了這些二進制的玩法後就能輕鬆的看動ForkJoinPool的邏輯代碼了。詳情見:二進制的一些玩法

1.初始化

先看下ForkJoinPool構造方法:

    /****
    parallelism: 並行度
    factory: 創建工作線程的工廠實現
    handler: 內部工作線程因爲未知異常而終止的回調處理
    asyncMode: 異步模式(對於任務的處理順序採用何種模式),true表示
    採用FIFO模式,false表示採用LIFO模式
    ***/
    public ForkJoinPool(int parallelism,
                        ForkJoinWorkerThreadFactory factory,
                        UncaughtExceptionHandler handler,
                        boolean asyncMode) {
        this(checkParallelism(parallelism),
             checkFactory(factory),
             handler,
             asyncMode ? FIFO_QUEUE : LIFO_QUEUE,
             "ForkJoinPool-" + nextPoolId() + "-worker-");
        checkPermission();
    }

再看下內部重載的構造方法:

private ForkJoinPool(int parallelism,
                     ForkJoinWorkerThreadFactory factory,
                     UncaughtExceptionHandler handler,
                     int mode,
                     String workerNamePrefix) {
    this.workerNamePrefix = workerNamePrefix;
    this.factory = factory;
    this.ueh = handler;
    this.config = (parallelism & SMASK) | mode;
    long np = (long)(-parallelism); // offset ctl counts
    this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}

這裏我們需要重點分析一下config,np和ctl
先看config,它等於(parallelism & SMASK) | mode;其中SMASK=0xffff;沒有任務的業務含義,而並行度parallelism 與SMASK進行邏輯與運算,其實就是保證parallelism 不大於SMASK,作者這裏有點多此一舉了,因爲私有的這個構造方法在傳入parallelism之前都會進行parallelism的大小判斷,都會保證parallelism不大於MAX_CAP(0x7fff),而MAX_CAP肯定是比SMASK小的。所以最終(parallelism & SMASK) | mode 可以簡化爲parallelism | mode。
我們看下mode有兩個值LIFO和FIFO:

static final int MODE_MASK    = 0xffff << 16;  // top half of int
static final int LIFO_QUEUE   = 0;
static final int FIFO_QUEUE   = 1 << 16;
static final int SHARED_QUEUE = 1 << 31;       // must be negative
//其中LIFO_QUEUE爲0這個很簡單,而FIFO_QUEUE爲1 << 16,轉換成二進制表示法就是:
0000000000000001 0000000000000000(第17位爲1)
而MAX_CAP(0x7fff)的二進制表示爲:
0000000000000000 0111111111111111
所以parallelism | mode結果爲兩種:即17位是否爲1用來表示模式,而低15位用來表示並行度。
當我們需要從config中取出模式的時候只需要用掩碼MODE_MASK與config進行邏輯與運算(這是掩碼的玩法),
因爲MODE_MASK = 0xffff << 16二進制表示爲:1111111111111111 0000000000000000,邏輯與運算
就取到了高16位,我們只用關注高16位是否爲0(LIFO),或者1(FIFO),或者最高位爲1(SHARED)

所以config可以總結表示爲:
config示意圖
再來看下np = (long)(-parallelism),即並行度補碼轉換爲long型(64位),這裏關於補碼的運算不深入講,自行百度,最終的結果就是:以MAX_CAP爲例,則np爲:

1111111111111111 1111111111111111 1111111111111111 1000000000000001

如果並行度爲1,則np爲:

1111111111111111 1111111111111111 1111111111111111 1111111111111111

那這個np有啥用? 我們再看下ctl。

((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
//我們先看AC_SHIFT和TC_SHIFT

// Active counts
private static final int  AC_SHIFT   = 48;
private static final long AC_UNIT    = 0x0001L << AC_SHIFT;
private static final long AC_MASK    = 0xffffL << AC_SHIFT;

// Total counts
private static final int  TC_SHIFT   = 32;
private static final long TC_UNIT    = 0x0001L << TC_SHIFT;
private static final long TC_MASK    = 0xffffL << TC_SHIFT;
private static final long ADD_WORKER = 0x0001L << (TC_SHIFT + 15); // sign
//從上面的簡單註釋我們可以看出,AC即Active counts,即活躍線程數,而TC即Total counts,
//即總的線程數
其中np << AC_SHIFT代表np左移動48位,即低16位變成了高16位,所以ctl的64位中,高49~64位代表活躍線程數的負數,
同理np<<TC_SHIFT是將np左移32位,即低16位移動到了高33~48位,所以ctl的高33~48位代表線程總數的負數。
最後通過邏輯或運算合併到一起,這裏還經過了掩碼運算,例如& AC_MASK,就是爲了取對應的位數。

所以那並行度MAX_CAP爲例,初始化的ctl爲:

1000000000000001 1000000000000001 0000000000000000 0000000000000000 

暫且先不管ctl這樣設計的用處,後續用到再分析。

2.核心方法

通過代碼發現FrokJoinPool提供給外界的核心方法中,提交任務的有三類:submit、invoke、execute,然後還有一個shutdown方法,接下來一個一個分析。

2.1 invoke方法

/***
運行給定的任務,任務完成後返回結果。
如果運算期間遇到未受檢查的異常或者錯誤,這些異常和錯誤將被當作是本次調用的結果重新拋出,
重新拋出的異常與普通的異常無異,但是,在可能的情況下,包含當前線程和真實遇到異常的線程
的棧信息,只有後者才能辦到。
****/
public <T> T invoke(ForkJoinTask<T> task) {
    if (task == null)
        throw new NullPointerException();
    externalPush(task);
    return task.join();
}

該方法結構很簡單,首先調用externalPush方法,最後返回任務join的結果。接下來看下核心的externalPush方法。

externalPush

/**
嘗試將給定的任務添加到提交者的當前隊列(其中一個submission queue).
在篩選externalSubmit需求時,只有(大部分)最常見的路徑在此方法中被直接處理
**/
final void externalPush(ForkJoinTask<?> task) {
    WorkQueue[] ws; WorkQueue q; int m;
    int r = ThreadLocalRandom.getProbe();  //線程隨機數
    int rs = runState; //runState運行狀態,初始化爲0
    //workQueues爲整個線程池的工作者隊列(其實就是一個數組)
    //當工作隊列不爲空,長度至少爲1(並且m這裏表示ws工作數組當前能夠表示的最大下標)
    //其中m&r表示隨機數不大於m,然後&SQMASK(SQMASK = 0x007e;)相當於只取偶數,並且偶數不大於0x7e(126),這裏從隨機的偶數槽位取出WorkQueue不爲空,
    //r!=0, 說明不是第一個
    //rs>0 說明運行狀態被初始化過
    //CAS:並且併發控制所取得的WorkQueue成功(qlock字段1表示鎖定)
    if ((ws = workQueues) != null && (m = (ws.length - 1)) >= 0 &&
        (q = ws[m & r & SQMASK]) != null && r != 0 && rs > 0 &&
        U.compareAndSwapInt(q, QLOCK, 0, 1)) {
        ForkJoinTask<?>[] a; int am, n, s;
        if ((a = q.array) != null && //WorkQueue中的任務數組不爲空
            (am = a.length - 1) > (n = (s = q.top) - q.base)) { //數組未裝滿?
            int j = ((am & s) << ASHIFT) + ABASE; //獲取本次要放入元素的偏移量
            U.putOrderedObject(a, j, task);  //放入任務
            U.putOrderedInt(q, QTOP, s + 1); //top+1
            U.putIntVolatile(q, QLOCK, 0);   //釋放任務隊列(WorkQueue)的鎖
            if (n <= 1) //放入任務成功後,如果發現放入任務前最多隻有一個任務在隊列中,當前放入任務成功後需要手動喚醒工作者,避免新加入的任務無法運行。??
                signalWork(ws, q);
            return;
        }
        U.compareAndSwapInt(q, QLOCK, 1, 0); //釋放鎖
    }
    //在隊列未進行初始化等條件下,代碼直接來到此處,這裏未初始化包括workQueues[]未初始化
    //或者對應下標的WorkQueue未初始化,或者WorkQueue中的ForkJoinTask<?>[]未初始化或者滿
    externalSubmit(task);
}

從上面代碼可以看到:

  • 1.externalPush方法主要的目標是將用戶提交的任務放入到線程池的下標爲偶數的工作隊列(workQueues[]的下標爲偶數的工作隊列)的任務列表中去。
  • 2.當線程次中工作隊列數組未初始,或者獲取到的工作隊列中的任務數組未初始化或者容量滿,都轉爲調用externalSubmit方法,看來該方法包含了所有的初始化和擴容邏輯。

接下來重點看一下externalSubmit方法的執行邏輯。

externalSubmit

該方法的是一個完整的外部提交任務入任務隊列的邏輯,大致的流程入下圖:
externalSubmit流程
詳細的代碼註釋如下:

private void externalSubmit(ForkJoinTask<?> task) {
    int r;                                    // initialize caller's probe
    if ((r = ThreadLocalRandom.getProbe()) == 0) {
        ThreadLocalRandom.localInit();
        r = ThreadLocalRandom.getProbe(); //獲取線程隨機數
    }
    for (;;) {
        WorkQueue[] ws; WorkQueue q; int rs, m, k;
        boolean move = false;
        if ((rs = runState) < 0) { //線程池的狀態爲終止狀態
            tryTerminate(false, false);     // help terminate
            throw new RejectedExecutionException();
        }
        //線程池爲初始化狀態
        else if ((rs & STARTED) == 0 ||     // initialize
                 ((ws = workQueues) == null || (m = ws.length - 1) < 0)) {
            int ns = 0;
            rs = lockRunState();  //獲取鎖,這個方法一會兒單獨解析
            try {
                if ((rs & STARTED) == 0) {//再判斷一次狀態是否爲初始化,因爲在lockRunState過程中有可能狀態被別的線程更改了
                    //初始化stealcounter的值(任務竊取計數器)
                    U.compareAndSwapObject(this, STEALCOUNTER, null,
                                           new AtomicLong());
                    // create workQueues array with size a power of two
                    //這裏英語註釋已經很明白了,初始化任務隊列數組,大小是2的k次冪
                    int p = config & SMASK; // ensure at least 2 slots //這裏取config的低16位(有值的只有低15位),表示並行度(見前面的config初始化)。
                    //這裏通過前面的代碼我們知道,並行度默認是跟cpu核數相同,但是極端也可以到達MAX_CAP這麼多。
                    int n = (p > 1) ? p - 1 : 1;
                    n |= n >>> 1; n |= n >>> 2;  n |= n >>> 4;
                    n |= n >>> 8; n |= n >>> 16; n = (n + 1) << 1;
                    workQueues = new WorkQueue[n];
                   //關於n,大家可以看一下, 假如p=1,或者2,n最終就等於4,
                   //當p=MAX_CAP的時候,n最終等於2的16次方
                  //更有趣的是這個位移,一個數n通過→移動1,2,4,8,16,然後分別於自己取或運算,
                  //其實的目的就是把數n二進制表示法的從非零位開始的低位全部變爲1,然後最後一步n+1,其實是進1,低位清0,然後再左移1位(容量翻倍)//
                  //所以最終的結論就是:工作隊列數組的大小,與並行度二進制表示後低位有效位數(k)有關,大小等於2的k+1次方。
                  //具體對比可以看下面的**表格2-1**
                   
                    ns = STARTED; //
                }
            } finally {
                unlockRunState(rs, (rs & ~RSLOCK) | ns); //解鎖單獨說
            }
        }
        else if ((q = ws[k = r & m & SQMASK]) != null) { //這裏取偶數槽位(或者0),並且低於126(SQMASK確定的,所以這裏我們也可以看到,偶數槽位最多64個)
            if (q.qlock == 0 && U.compareAndSwapInt(q, QLOCK, 0, 1)) {//判斷這個工作隊列的鎖定狀態
                ForkJoinTask<?>[] a = q.array;
                int s = q.top;
                boolean submitted = false; // initial submission or resizing
                try {                      // locked version of push
                    //如果任務數組不爲空,並且數組長度大於已經放入的元素+1(說明至少還能再放一個)
                    //否則對數組進行擴容
                    if ((a != null && a.length > s + 1 - q.base) ||
                        (a = q.growArray()) != null) {
                        //這裏的邏輯非常有意思,請見後面的詳細講解
                        //這裏的目的是計算本次應該放入的任務在內存中的偏移位置,其實取到的就是top<<ASHIFT + ABASE
                        int j = (((a.length - 1) & s) << ASHIFT) + ABASE;
                        U.putOrderedObject(a, j, task); //放入任務
                        U.putOrderedInt(q, QTOP, s + 1); //top+1
                        submitted = true; //任務提交成功標誌
                    }
                } finally {
                    U.compareAndSwapInt(q, QLOCK, 1, 0);
                }
                if (submitted) { //提交任務成功,喚醒工作線程
                    signalWork(ws, q);
                    return;  //唯一的任務提交自旋出口,任務提交成功返回
                }
            }
            //標記任務未成功提交,需要再次計算隨機數,然後再嘗試
            move = true;                   // move on failure
        }
        //如果找到的槽爲空,則需要初始化WorkQueue
        else if (((rs = runState) & RSLOCK) == 0) { // create new queue
            q = new WorkQueue(this, null);  //初始化
            q.hint = r; //設置工作隊列的任務竊取線索
            q.config = k | SHARED_QUEUE; //將工作隊列所在池的位置和任務隊列模式記錄到config中
            q.scanState = INACTIVE; //工作隊列狀態爲未活動,小於0
            rs = lockRunState();           // publish index //鎖定
            if (rs > 0 &&  (ws = workQueues) != null &&
                k < ws.length && ws[k] == null)
                ws[k] = q;                 // else terminated //將q放入工作隊列池
            unlockRunState(rs, rs & ~RSLOCK);
        }
        else
            move = true;                   // move if busy
        if (move)
            r = ThreadLocalRandom.advanceProbe(r);
    }
}

以下是並行度與容量的關係,注意並行度p需要減1
表格2-1

並行度p 並行度-1後二進制表示 容量
1/2 01 4
3/4 1x 8
5/6/7/8 1xx 16
9~16 1xxx 32

從表格我們也能看出,爲什麼int n = (p > 1) ? p - 1 : 1;參與運行的n需要減去1,其實是爲了2的k次方位減1降低1位,這樣纔有1與2一組,3與4一組,5到8一組,否則就會變爲,1爲一組,2與3一組,4到7一組。

計算任務數組放入元素的內存偏移量

int j = (((a.length - 1) & s) << ASHIFT) + ABASE;

由代碼得知,a.length - 1大於s,所以最後可以寫爲int j = s<<ASHIFT +ABASE
其中ASHIFT和ABASE的代碼如下:

private static final sun.misc.Unsafe U;
private static final int  ABASE;
private static final int  ASHIFT;
private static final long QTOP;
private static final long QLOCK;
private static final long QCURRENTSTEAL;
static {
    try {
        U = sun.misc.Unsafe.getUnsafe();
        Class<?> wk = WorkQueue.class;
        Class<?> ak = ForkJoinTask[].class;
        QTOP = U.objectFieldOffset
            (wk.getDeclaredField("top"));
        QLOCK = U.objectFieldOffset
            (wk.getDeclaredField("qlock"));
        QCURRENTSTEAL = U.objectFieldOffset
            (wk.getDeclaredField("currentSteal"));
        ABASE = U.arrayBaseOffset(ak); //獲取ForkJoinTask[]數組對象頭的偏移位置
        int scale = U.arrayIndexScale(ak); //獲取ForkJoinTask[]數組中每個元素引用所佔的大小
        if ((scale & (scale - 1)) != 0)  //確保scale爲2的k次方
            throw new Error("data type scale not a power of two");
        //Integer.numberOfLeadingZeros(scale)表示scale的高位連續0的位數
        //然後31減去Integer.numberOfLeadingZeros(scale),其實就是表示scale低位有多少位
        //例如scale=4, 二進制表示爲:0000000000000000 0000000000000100,所以ASHIFT=2
        ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); 
    } catch (Exception e) {
        throw new Error(e);
    }
}

那麼int j = s<<ASHIFT +ABASE到底表示什麼呢? 我們先不管它, 我們來想一下,如果我們正常用加減乘除來計算數組元素便宜位置應該如何就算,答案是:scaletop+ABASE,其中top表示下一次要放置元素的下標,那麼這個公式又怎麼和int j = s<<ASHIFT +ABASE扯上關係了(其中s就算top哈)?我們看到ABASE都可以幹掉,然後就剩下scale * top 與 top<<ASHIFT了,由二進制的一些玩法可以知道,一個數mn,如果n是2的k次方,那麼mn可以直接表示爲m<<k,那麼再看scale * top, 其中scale就算2的k次方,所以完全就可以表示爲top << k, 那麼這個k,其實就是ASHIFT,所以scale * top 就等於 top<<ASHIFT, 最終總結就是:int j = s(top)<<ASHIFT +ABASE 與 scaletop+ABASE等價, 看到這裏,很想說一句cao。

線程池加鎖 lockRunState/unlockRunState

由上面的邏輯我們可以看到,在初始化線程池或者在向線程池中添加任務隊列時都需要鎖定線程池,這裏不由自主的就產生了一個疑問:爲什麼不用ReentrantLock呢?
我們先看lockRunState的代碼:


//在開始看代碼之前,我們要來看一下runState的各個位代表的含義
// runState bits: SHUTDOWN must be negative, others arbitrary powers of two
private static final int  RSLOCK     = 1;       //線程池被鎖定
private static final int  RSIGNAL    = 1 << 1;  //代表有線程需要喚醒
private static final int  STARTED    = 1 << 2;  //代表已經初始化
private static final int  STOP       = 1 << 29; //線程池停止
private static final int  TERMINATED = 1 << 30; //線程池終止
private static final int  SHUTDOWN   = 1 << 31; //線程池關閉
//二進制表示爲
1110000000000000 0000000000000111



//其中RSLOCK=1,如果runState & RSLOCK == 0,說明沒有加鎖,則通過CAS快速嘗試加鎖,如果失敗則進入awaitRunStateLock()
private int lockRunState() {
    int rs;
    return ((((rs = runState) & RSLOCK) != 0 ||
             !U.compareAndSwapInt(this, RUNSTATE, rs, rs |= RSLOCK)) ?
            awaitRunStateLock() : rs);
}

//該方法保證加鎖一定成功
private int awaitRunStateLock() {
    Object lock;
    boolean wasInterrupted = false; //線程中斷標記
    for (int spins = SPINS, r = 0, rs, ns;;) {
        if (((rs = runState) & RSLOCK) == 0) { //判斷是否加鎖(==0表示未加鎖)
            //未加鎖的情況下, 通過CAS加鎖(即runState最低位變爲1)
            if (U.compareAndSwapInt(this, RUNSTATE, rs, ns = rs | RSLOCK)) {
                if (wasInterrupted) {
                    try {
                        Thread.currentThread().interrupt(); //重置線程中斷標記
                    } catch (SecurityException ignore) {
                    }
                }
                return ns;//加鎖成功返回最新的runState值,這也是整個自旋的唯一出口
            }
        }
        else if (r == 0) //當前鎖被其它線程佔有,更新線程隨機數
            r = ThreadLocalRandom.nextSecondarySeed();
        else if (spins > 0) { //這裏是隨機的空轉,初始SPINS是0,這裏不會進入
            r ^= r << 6; r ^= r >>> 21; r ^= r << 7; // xorshift
            if (r >= 0)
                --spins;
        }
        //如果是初始化狀態加鎖,鎖被其它線程佔有,則讓出CPU,讓其它線程加快初始
        else if ((rs & STARTED) == 0 || (lock = stealCounter) == null)
            Thread.yield();   // initialization race
        //如果其它線程持有鎖,並且線程池已經初始化,則將需要喚醒位標記爲1
        else if (U.compareAndSwapInt(this, RUNSTATE, rs, rs | RSIGNAL)) {
            synchronized (lock) { //通過該實例的屬性加鎖
                if ((runState & RSIGNAL) != 0) {
                //這裏加鎖後再判斷了一次,如果RSIGNAL位已經爲0,
                //則說明在加鎖前剛好有線程進行了喚醒動作,所以這裏不再等待直接到else裏喚醒,否則說明RSIGNAL位==1,則讓當前線程等待。
                    try {
                        lock.wait();
                    } catch (InterruptedException ie) {
                        if (!(Thread.currentThread() instanceof
                              ForkJoinWorkerThread))
                            wasInterrupted = true;
                    }
                }
                else
                    lock.notifyAll();
            }
        }
    }
}

總體來看加鎖的邏輯:
1.首先是看其它線程是否已經持有鎖,沒有則通過CAS改變RSLOCK位,完成加鎖,如果加鎖不成功則再自旋。
2.如果其它線程持有了鎖,看下其它線程是不是在初始化線程池,如果是,則放棄本次CPU,否則再自旋。
3.如果其它線程持有了鎖,並且線程池已經初始化,則設置喚醒標誌位RSIGNAL,讓當前競爭鎖的線程睡眠,這個睡眠的動作是同步執行的,如果在睡眠前發現RSIGNAL位已經被釋放,則執行喚醒操作。

再來看下開頭提出的問題:爲什麼不用ReentrantLock?我們可以看到,這裏自行實現的鎖邏輯要比用ReentrantLock更細,這裏在競爭鎖的過程中會判斷線程池狀態,如果是初始化狀態,則會放棄CPU,減少競爭;其次是作者將鎖定標誌位,喚醒位等一同設計到了runState這個字段中,如果用ReentrantLock,則就不需要這兩個字段。 總的來說如果用ReentrantLock是肯定能達到線程池加鎖的目的,只是略微粗一點而已。

再看看unlockRunState的實現:

/**
 * Unlocks and sets runState to newRunState.
 *
 * @param oldRunState a value returned from lockRunState
 * @param newRunState the next value (must have lock bit clear).
 */
 //從註釋可以看出,newRunState一定是將鎖定標誌位清零了的
private void unlockRunState(int oldRunState, int newRunState) {
    //直接嘗試CAS更新
    //如果更新失敗,說明RSIGNAL位從0變成了1(oldRunState中的RSIGNAL爲0),又因爲newRunState沒有改變RSIGNAL,所以newRunState中的RSIGNAL爲0.
    //當然,如果這裏更新成功,則說明RSIGNAL沒變,有可能是持有鎖的過程中沒有其它線程競爭,所以不需要喚醒動作,如果有其它線程競爭,在前一個持有者釋放鎖的時候一定會把RSGINAL位強制設置爲0,所以被喚醒的線程重新獲取到鎖的時候RSIGNAL位一定爲0,這樣就形成了一個良性循環。
    if (!U.compareAndSwapInt(this, RUNSTATE, oldRunState, newRunState)) {
    //CAS失敗,代表oldRunState中的RSIGNAL位改變了(0變成了1)
        Object lock = stealCounter;
        //強制替換,更新了RSIGNAL位和RSLOCK位,都變爲0
        runState = newRunState;              // clears RSIGNAL bit
        if (lock != null)
         //由於RSIGNAL位變爲0,所以要喚醒全部的等待線程
            synchronized (lock) { lock.notifyAll(); }
    }
}

這裏代碼雖短,但是邏輯很有趣,通常的邏輯是釋放鎖再喚醒其它線程,但是這裏的邏輯剛好是CAS失敗才喚醒,爲什麼?
因爲失敗的唯一原因可能是有線程競爭而導致等待需要喚醒,如果CAS成功,則不需要做任何多餘動作。

signalWork

在任務提交到工作隊列成功後會調用signalWork方法進行工作線程喚醒,下面就來詳細的看一下signalWork做了什麼事情。

/**
 * Tries to create or activate a worker if too few are active.
 *
 * @param ws the worker array to use to find signallees
 * @param q a WorkQueue --if non-null, don't retry if now empty
 */
 //註釋:如果太少的活動線程,則該方法會創建或者激活一個工作者
final void signalWork(WorkQueue[] ws, WorkQueue q) {
    long c; int sp, i; WorkQueue v; Thread p;
    while ((c = ctl) < 0L) {                       // too few active
        if ((sp = (int)c) == 0) {                  // no idle workers
            if ((c & ADD_WORKER) != 0L)            // too few workers
                tryAddWorker(c);
            break;
        }
        if (ws == null)                            // unstarted/terminated
            break;
        if (ws.length <= (i = sp & SMASK))         // terminated
            break;
        if ((v = ws[i]) == null)                   // terminating
            break;
        int vs = (sp + SS_SEQ) & ~INACTIVE;        // next scanState
        int d = sp - v.scanState;                  // screen CAS
        long nc = (UC_MASK & (c + AC_UNIT)) | (SP_MASK & v.stackPred);
        if (d == 0 && U.compareAndSwapLong(this, CTL, c, nc)) {
            v.scanState = vs;                      // activate v
            if ((p = v.parker) != null)
                U.unpark(p);
            break;
        }
        if (q != null && q.base == q.top)          // no more work
            break;
    }
}

更新中…

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