Phaser實現源碼剖析

    Phaser是一個可以重複利用的同步柵欄,功能上與CyclicBarrier和CountDownLatch相似,不過提供更加靈活的用法。也就是說,Phaser的同步模型與它們差不多。一般運用的場景是一組線程希望同時到達某個執行點後(先到達的會被阻塞),執行一個指定任務,然後這些線程才被喚醒繼續執行其它任務。
    Phaser一般是定義一個parties數(parties一般代指需要進行同步的線程),當這些parties到達某個執行點,就會調用await方法,表示到達某個階段(phase),然後就會阻塞直到有足夠的parites數(也就是線程數)都調用過await方法後,這些線程纔會被逐個喚醒,另外,在喚醒之前,可以選擇性地執行某個定製的任務。Phaser對比起CyclicBarrier,不僅它是可以重複同步,並且parties數是可以動態註冊的,另外還提供了非阻塞的arrive方法表示先到達階段等,大大提高了同步模型的靈活性,當然了,實現也會相對複雜。

註冊(Registration)
    和其它柵欄不一樣,在phaser上進行同步的註冊parites數可能會隨着時間改變而不同。可以在任何時間註冊任務(調用register,bulkRegister方法,或者可以初始化parties數的構造函數形式),另外還可以在任何到達柵欄的時候反註冊(arriveAndDeregister方法)。作爲大部分同步結構的基礎,註冊和反註冊只會影響內部計數;它們沒有做任何額外的內部記錄,所以任務不能夠查詢它們是否被註冊。(不過你可以繼承這個類以引入這樣的記錄)

同步(Synchronization)
    就像CyclicBarrier一樣,Phaser可以重複地等待。arriveAndAwaitAdvance方法與CyclicBarrier.await作用類似。每一代(generation)的phaser有一個關聯的階段號(phase number)。階段號從0開始,當所有parties都到達階段的時候就會加一,直到Integer.MAX_VALUE後返回0。階段號的使用允許在到達某個階段以及在等待其它parites的時候,通過以下兩類可以被任何註冊過的party調用的方法,執行單獨的操作:
    1.到達(Arrival)
        arrive方法和arriveAndDeregister方法記錄到達。這些方法不會阻塞,但會返回一個關聯的到達階段號(arrival phase number),也就是,phaser在到達以後所用的階段號。當最後的party到達一個給定的階段,就可以執行可選的操作,並且階段號自加一。這些操作都會被觸發階段增加的party執行,並且會被可重寫方法onAdvance(同時管理Phaser的終結)安排管理。重寫onAdvance方法比起CyclicBarrier提供的柵欄操作很相似,但更加靈活。
    2.等待(Waiting)
        awaitAdvance方法需要一個表示到達階段號的參數,並在phaser增加(或者已經在)不同的階段的時候返回。與CyclicBarrier類似結構不同,awaitAdvance方法會在等待線程被中斷的時候繼續等待。當然也有可中斷和超時版本,但是當任務等待發生中斷或者超時遇到的異常也不會改變phaser的狀態。如果必要,你可以在這些異常的處理器裏執行關聯的恢復操作,一般是在調用forceTermination之後恢復。Phaser可能也會被執行在ForkJoinPool中的任務使用,這樣當其它線程在等待phaser增加被阻塞的時候,就可以確保有效平行地執行任務。

終結(Termination)

phaser可能進入一個終結狀態,可以通過isTerminated來檢查。當終結的時候,所有的同步方法都不會在等待下一個階段而直接返回,返回一個負值來表示該狀態。類似地,在終結的時候嘗試註冊沒有任何效果。當onAdvance調用返回true的時候就會觸發終結。onAdvance默認實現爲當一個反註冊導致註冊parties數降爲0的時候返回true。當phser要控制操作在一個固定得迭代次數時,就可以很方便地重寫這個方法,噹噹前階段號到達閥值得時候就返回true導致終結。forceTermination方法也時另一個可以突然釋放等待線程並且允許它們終結。


堆疊(Tiering)

Phaser可以被堆疊在一起(也就是說,以樹形結構構造)來降低競爭。Phaser的parties數很大的時候,以一組子phasers共享一個公共父親能夠減輕嚴重的同步競爭的成本。這樣做可以大大提供吞吐量,但同時也會導致每個操作的更高的成本。

    在一棵堆疊的phaser樹中,子phaser在父親上的註冊和反註冊都會被自動管理。當子phaser的註冊parties樹爲非0的時候,子phaser就會註冊到父親上。當由於arriveAndDeregister的調用使註冊的parties數變爲0時,子phaser就會從父親中反註冊。這樣就算父phaser的所有parties都到達了階段,也必須等待子phaser的所有parties都到達了階段並顯式調用父phaser的awaitAdvance纔算到達新的階段。反之亦然。這樣父phaser或者子phaser裏註冊過的所有parties就可以一起互相等待到新的階段。另外,在這個堆疊結構的實現裏,可以確保root結點必然是最先更新階段號,然後纔到其子結點,逐漸傳遞下去。

    +------+      +------+      +------+
    | root |  <-- |parent| <--  | this |
    +------+      +------+      +------+


   parties:3+1   parties:3+1    parties:3

    如上圖所示,如果parties數多的時候,可以根據堆疊成爲一顆樹,這裏假設root和parent和this都各初始化3個parties數,然後如果當前結點this有註冊parties數,則會在parent上註冊一個parties,因此事實上root和parent都註冊了4個parties數。這樣,如果this結點的3個parties數都到達了,就會調用parent的arrive,把parties數減去一,然後parent等待自己3個parties數都到達,就會調用root來減去一,這樣root的3個parties數都到達就會一同釋放所有等待結點,就實現了整棵樹parties之間同步等待的功能。另外這個結構也很容易看到root結點是最快進行階段增長的。這樣做最大的好處就是減少對同一個state變量的CAS競爭帶來的性能下降,不過同時每個同步操作也會增加相應的負擔(每次獲取狀態都要和root進行階段同步),所以一般在高併發下造成的性能下降才考慮。

監控(Monitoring)

同步方法只能被註冊的parties調用時,phaser的當前狀態可以被任何調用者監控。在任何時刻,有getRegisteredParties總共的parties,其中,有getArrivedParties個parites到達getPhase的當前階段。當剩下getUnarrivedParties個parties到達,phase增加。這些方法的返回值可能反映短暫的狀態,因此一般在同步控制中不太有用。toString方法以一種可以方便信息監控的格式返回這些狀態的快照。

    Phaser的具體用法可以參考例子:這裏

源碼剖析

內部狀態表示

    Phaser內部對於狀態(包括parties註冊數、階段號、未到達的parties數等)管理都使用一個volatile long型變量,同時利用CAS進行更新。這樣做就可以保證在狀態改變時,保持所有狀態一致改變,這是實現無鎖算法的基礎之一。
    private volatile long state;


    private static final int  MAX_PARTIES     = 0xffff;
    private static final int  MAX_PHASE       = Integer.MAX_VALUE;
    private static final int  PARTIES_SHIFT   = 16;
    private static final int  PHASE_SHIFT     = 32;
    private static final int  UNARRIVED_MASK  = 0xffff;      // to mask ints
    private static final long PARTIES_MASK    = 0xffff0000L; // to mask longs
    private static final long COUNTS_MASK     = 0xffffffffL;
    private static final long TERMINATION_BIT = 1L << 63;


    // some special values
    private static final int  ONE_ARRIVAL     = 1;
    private static final int  ONE_PARTY       = 1 << PARTIES_SHIFT;
    private static final int  ONE_DEREGISTER  = ONE_ARRIVAL|ONE_PARTY;
    private static final int  EMPTY           = 1;


    //內部狀態輔助方法
    private static int unarrivedOf(long s) {
        int counts = (int)s;
        return (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);
    }


    private static int partiesOf(long s) {
        return (int)s >>> PARTIES_SHIFT;
    }


    private static int phaseOf(long s) {
        return (int)(s >>> PHASE_SHIFT);
    }


    private static int arrivedOf(long s) {
        int counts = (int)s;
        return (counts == EMPTY) ? 0 :
            (counts >>> PARTIES_SHIFT) - (counts & UNARRIVED_MASK);
    }
    state變量爲long類型,長度爲64位,其中:
    未達到parties數     (0-15位)
    註冊的parties數     (16-31位)
    當前階段號          (32-62位)
    結束狀態標識        (63位 符號位)
    把符號位設置位結束狀態可以簡單判斷state是否爲負表示是否結束。另外,如果當phaser沒有任何註冊parties數,則會用一個無效狀態EMPTY(0個已註冊和1個未到達parites數)來區分其它狀態。
    除此之外,phaser定義了一些靜態常量方便對state變量進行移位解析,如*_SHIFT移位和*_MASK掩位。另外還有一些特殊值方便計算。還有一些輔助方法能夠從state提取某些狀態值。

動態註冊
    接着看看動態註冊parties的實現。動態註冊有兩個可用的接口,register方法和bulkRegister方法,其中register方法默認註冊一個party數,bulkRegister方法可以註冊數加上已經註冊的,最大不超過MAX_PARTIES。
    public int register() {
        return doRegister(1);
    }


    public int bulkRegister(int parties) {
        if (parties < 0)
            throw new IllegalArgumentException();
        if (parties == 0)
            return getPhase();
        return doRegister(parties);
    }
    兩者實現都很簡單,bulkRegister方法中添加了對parties數的檢查。兩個方法都調用了doRegister方法實現。
    private int doRegister(int registrations) {
        // adjustment to state
        long adjust = ((long)registrations << PARTIES_SHIFT) | registrations;
        final Phaser parent = this.parent;
        int phase;
        for (;;) {
            long s = (parent == null) ? state : reconcileState();
            int counts = (int)s;
            int parties = counts >>> PARTIES_SHIFT;
            int unarrived = counts & UNARRIVED_MASK;
            if (registrations > MAX_PARTIES - parties)
                throw new IllegalStateException(badRegister(s));
            phase = (int)(s >>> PHASE_SHIFT);
            if (phase < 0)
                break;
            if (counts != EMPTY) {                  // not 1st registration
                if (parent == null || reconcileState() == s) {
                    if (unarrived == 0)             // wait out advance
                        root.internalAwaitAdvance(phase, null);
                    else if (UNSAFE.compareAndSwapLong(this, stateOffset,
                                                       s, s + adjust))
                        break;
                }
            }
            else if (parent == null) {              // 1st root registration
                long next = ((long)phase << PHASE_SHIFT) | adjust;
                if (UNSAFE.compareAndSwapLong(this, stateOffset, s, next))
                    break;
            }
            else {
                synchronized (this) {               // 1st sub registration
                    if (state == s) {               // recheck under lock
                        phase = parent.doRegister(1);
                        if (phase < 0)
                            break;
                        // finish registration whenever parent registration
                        // succeeded, even when racing with termination,
                        // since these are part of the same "transaction".
                        while (!UNSAFE.compareAndSwapLong
                               (this, stateOffset, s,
                                ((long)phase << PHASE_SHIFT) | adjust)) {
                            s = state;
                            phase = (int)(root.state >>> PHASE_SHIFT);
                            // assert (int)s == EMPTY;
                        }
                        break;
                    }
                }
            }
        }
        return phase;
    }
    doRegister方法做了以下事情:
    首先計算註冊後要當前state要調整的值adjust,注意adjust把未到達的parties數和註冊的parties數都設爲registrations;接着就進入自循環,首先考慮到堆疊的情況(parent不爲null),就要調用reconcileState方法與parent的階段號同步,並計算出未註冊前正確的state值,然後再依次計算註冊parties數parties,未到達數unarrived,階段號phase,並且判斷註冊後是否超過MAX_PARTIES等一系列的準備工作,接下來就是三個判斷區分三種不同的情況:
    (1)如果counts!=EMPTY(也即已經有parties註冊),則此時如果parent不爲null,則要調用reconcileState方法和當前的狀態s對比,這是因爲要保證parent和子phaser的階段號保持一致。如果一致或者parent爲null(沒有堆疊),此時可以先拿unarrived值與0判斷,如果爲0,則表示所有parties已經到達,階段號增加的情況,因此調用root.internalAwaitAdvance(在沒有堆疊的情況下,root爲this,如果發生堆疊,root則爲整棵樹的根節點)來等待增加完成並再次循環同步,如果unarrived非0,則直接利用CAS把當前state添加adjust。如果CAS成功就可以break推出循環同步。事實上,這裏的unarrived判斷相當重要,假設這種情況,如果此時只剩下最後一個未到達的parties數,而它剛好調用了arrive到達階段,由於考慮到最後一個到達的party必須執行onAdvance函數,如果此時有新的註冊party,則要重新等待party完成,但已經造成錯誤的onAdvance調用,因此必須要在最後的party到達的時候禁止註冊。到達函數doArrive中有兩次CAS的調用(具體實現在下面詳細說明),第一次會把當前的未到達狀態數變爲0(這個CAS同時也是表示最後一個party已經到達),第二次在調用onAdvance後,會重新設置新的狀態值。在doRegister函數裏,unarrived的判斷可以防止第一個CAS以後(即最後一個party到達)時會執行CAS來註冊新的party(因爲此時unarrived==0,doRegister會進入阻塞等待doArrive完成第二個CAS)。
    (2)如果(1)判斷失敗,並且parent==null時,則表示這次是第一個註冊,因此直接算出新的state值,並且CAS即可,注意這裏沒有unarrived判斷的優化,因爲這次是第一個註冊,所以不會出現所有parties已經到達,階段號增加的情況。
    (3)如果(1)和(2)判斷失敗,則表示這次是子phaser的第一次註冊。這時我們使用來synchronized的內置鎖來防止併發出現,這時因爲子phaser第一次註冊,堆疊結構就必須要向parent註冊一次並只有一次。獲取synchronized鎖後,再重新檢查一次狀態是否發生變化,然後就調用parent.doRegister(1)向父phaser註冊自己。然後如果父phaser註冊成功(返回的phase>=0),就要利用自旋CAS把當前狀態添加adjust,注意這個自旋就是強制當前狀態值必須要成功註冊,這是因爲這個和父phaser註冊都屬於同一個原子事務,要在鎖裏面完成,否則可能會狀態不一致。
    doRegister大致邏輯如上,接着我們來看看其中的一些方法實現,首先是reconcileState。
    private long reconcileState() {
        final Phaser root = this.root;
        long s = state;
        if (root != this) {
            int phase, p;
            // CAS to root phase with current parties, tripping unarrived
            while ((phase = (int)(root.state >>> PHASE_SHIFT)) !=
                   (int)(s >>> PHASE_SHIFT) &&
                   !UNSAFE.compareAndSwapLong
                   (this, stateOffset, s,
                    s = (((long)phase << PHASE_SHIFT) |
                         ((phase < 0) ? (s & COUNTS_MASK) :
                          (((p = (int)s >>> PARTIES_SHIFT) == 0) ? EMPTY :
                           ((s & PARTIES_MASK) | p))))))
                s = state;
        }
        return s;
    }
    reconcileState主要目的是和根結點保持階段號同步。前面說過,如果出現堆疊情況,根結點是最先進行階段號增加,雖然階段號增加的操作會逐漸傳遞到子phaser,但某些同步操作,如動態註冊等,需要馬上獲悉整棵樹的階段號狀態避免多餘的CAS,因此就需要顯式和根結點保持同步。reconcileState實現就是如此,如果root!=this,即發生堆疊,就利用自旋CAS把當前修改狀態值,要注意的是由於階段號增加,會同時會把未到達的parties數設置爲原來的註冊parties數。主要實現都是移位和掩位操作,就不再贅述。

awaitAdvance實現
    Phaser的一個重要的同步操作就是awaitAdvance系列方法。awaitAdvance是阻塞等待指定階段號增長的一系列方法,包括
    int awaitAdvance(int phase)
    int awaitAdvanceInterruptibly(int phase) throws InterruptedException
    int awaitAdvanceInterruptibly(int phase, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException
    這三個方法的實現其實都大同小異,主要是增加來對中斷和超時的控制,具體實現如下:
    public int awaitAdvance(int phase) {
        final Phaser root = this.root;
        long s = (root == this) ? state : reconcileState();
        int p = (int)(s >>> PHASE_SHIFT);
        if (phase < 0)
            return phase;
        if (p == phase)
            return root.internalAwaitAdvance(phase, null);
        return p;
    }

    public int awaitAdvanceInterruptibly(int phase)
        throws InterruptedException {
        //省略一樣的代碼

        if (p == phase) {
            QNode node = new QNode(this, phase, true, false, 0L);
            p = root.internalAwaitAdvance(phase, node);
            if (node.wasInterrupted)
                throw new InterruptedException();
        }
        return p;
    }

     public int awaitAdvanceInterruptibly(int phase,
                                         long timeout, TimeUnit unit)
        throws InterruptedException, TimeoutException {
        long nanos = unit.toNanos(timeout);

        //省略一樣的代碼
        if (p == phase) {
            QNode node = new QNode(this, phase, true, true, nanos);
            p = root.internalAwaitAdvance(phase, node);
            if (node.wasInterrupted)
                throw new InterruptedException();
            else if (p == phase)
                throw new TimeoutException();
        }
        return p;
    }
    三者實現大致結構都一樣,首先獲取當前狀態值,如果堆疊則調用reconcileState獲取根結點同步後的狀態值。然後如果當前階段號與請求等待的階段號相等,則調用根結點的internalAwaitAdvance方法(根結點是最先進行階段號增長)。
    internalAwaitAdvance有兩個參數,一個是指定等待的階段號,另外一個是等待結點QNode,如果這個參數爲null則會在內部創建一個不會被中斷也不會超時的結點來加入隊列進行等待,否則就會把參數結點加入隊列,因此可以看到awaitAdvance的中斷和超時版本都會自己創建對應的結點傳入。先來看看結點QNode的實現:
    static final class QNode implements ForkJoinPool.ManagedBlocker {
        //省略其它成員變量以及構造函數
        QNode next;


        public boolean isReleasable() {
            if (thread == null)
                return true;
            if (phaser.getPhase() != phase) {
                thread = null;
                return true;
            }
            if (Thread.interrupted())
                wasInterrupted = true;
            if (wasInterrupted && interruptible) {
                thread = null;
                return true;
            }
            if (timed) {
                if (nanos > 0L) {
                    long now = System.nanoTime();
                    nanos -= now - lastTime;
                    lastTime = now;
                }
                if (nanos <= 0L) {
                    thread = null;
                    return true;
                }
            }
            return false;
        }


        public boolean block() {
            if (isReleasable())
                return true;
            else if (!timed)
                LockSupport.park(this);
            else if (nanos > 0)
                LockSupport.parkNanos(this, nanos);
            return isReleasable();
        }
    }
    Phaser的等待隊列使用的是Treiber無鎖算法的棧操作。例子實現可以參考這裏。首先可以注意到QNode類是實現了ForkJoinPool.ManagedBlocker接口,這個接口可以確保如果使用ForkJoinWorkerThread的時候就可以保持併發執行任務。
    首先看看isReleaseable的實現,接口定義函數返回true,就不需要接下來的block操作。因此如果當前階段號已經和指定階段號不相等,則返回true,另外在判斷中斷的時候,如果interruptible值(構造函數的時候)爲false,則會忽略中斷。接着就是一個典型的超時判斷邏輯。注意這裏在返回true之前都會把thread設爲null,表示不需要等待取消,不需要進行喚醒。
    block的實現也是很簡單,如果非超時就調用LockSupport.part,否則就調用超時版本parkNanos。
    接下來看看internalAwaitAdvance實現。
  //NCPU是當前CPU數量
    static final int SPINS_PER_ARRIVAL = (NCPU < 2) ? 1 : 1 << 8;


    private int internalAwaitAdvance(int phase, QNode node) {
        // assert root == this;
        releaseWaiters(phase-1);          // ensure old queue clean
        boolean queued = false;           // true when node is enqueued
        int lastUnarrived = 0;            // to increase spins upon change
        int spins = SPINS_PER_ARRIVAL;
        long s;
        int p;
        while ((p = (int)((s = state) >>> PHASE_SHIFT)) == phase) {
            if (node == null) {           // spinning in noninterruptible mode
                int unarrived = (int)s & UNARRIVED_MASK;
                if (unarrived != lastUnarrived &&
                    (lastUnarrived = unarrived) < NCPU)
                    spins += SPINS_PER_ARRIVAL;
                boolean interrupted = Thread.interrupted();
                if (interrupted || --spins < 0) { // need node to record intr
                    node = new QNode(this, phase, false, false, 0L);
                    node.wasInterrupted = interrupted;
                }
            }
            else if (node.isReleasable()) // done or aborted
                break;
            else if (!queued) {           // push onto queue
                AtomicReference<QNode> head = (phase & 1) == 0 ? evenQ : oddQ;
                QNode q = node.next = head.get();
                if ((q == null || q.phase == phase) &&
                    (int)(state >>> PHASE_SHIFT) == phase) // avoid stale enq
                    queued = head.compareAndSet(q, node);
            }
            else {
                try {
                    ForkJoinPool.managedBlock(node);
                } catch (InterruptedException ie) {
                    node.wasInterrupted = true;
                }
            }
        }


        if (node != null) {
            if (node.thread != null)
                node.thread = null;       // avoid need for unpark()
            if (node.wasInterrupted && !node.interruptible)
                Thread.currentThread().interrupt();
            if (p == phase && (p = (int)(state >>> PHASE_SHIFT)) == phase)
                return abortWait(phase); // possibly clean up on abort
        }
        releaseWaiters(phase);
        return p;
    }
    函數做了以下事情:
    1、調用releaseWaiters釋放上一個階段的等待隊列;
    2、進入while循環,判斷條件是保證指定的階段和當前階段保持一致,然後有4個判斷分支,代表不同情況:
    (1)如果參數node爲null,即請求的是非阻塞超時等待,接着是一個有關自旋等待的邏輯,考慮到在多核CPU上短時間大量線程喚醒相當慢,因此在這裏準備阻塞先添加一個簡單的自旋邏輯。具體是這樣:初始化一個自旋次數spins爲SPINS_PER_ARRIVAL,當此前的未到達parties數unarrived和上次記錄的未到達parties數lastUnarrived不相等的時候,並且parites數少於當前CPU數量,則會給當前自旋次數添加一個SPINS_PER_ARRIVAL常量,這樣在下一次到達之前都會自旋spins次,如果此時出現階段號增長,則會退出自旋,就可以避免接下來的阻塞邏輯,但如果在自旋spins次階段號仍然沒有遞增(如果此時發生中斷則取消自旋進入阻塞),則創建一個非中斷超時結點,準備進入等待隊列。
    (2)如果(1)判斷失敗,則調用已經創建node的isReleaseable方法,判斷是否是否因爲中斷或者超時等取消當前等待;
    (3)這裏是一個入隊操作,用queued變量保證只入隊一次。另外,考慮到上一階段裏如果有結點在釋放的時候,剛好當前階段有入隊操作的話會有競爭產生,因此這裏採用了兩個隊列,偶數隊列(evenQ)和奇數隊列(oddQ)。接着按照Treiber算法的模型,把請求結點入隊,注意入隊前需要再次判斷當前階段是否已經增加。
    (4)最後就是進入阻塞狀態,這裏調用ForkJoinPool.managedBlock方法把結點阻塞,直到階段號增長被喚醒,或者發生中斷或超時等取消等待。
    3、while循環退出後,接着就是結點node狀態清理,包括清空thread引用,以及必要的中斷狀態復位,另外如果在中斷和超時的情況下還需要調用abortWait釋放隊列裏面同樣是中斷和超時的結點。
    4、最後再次調用releaseWaiters釋放當前階段號的等待隊列。

    這裏順便看看releaseWaiters和abortWait的實現。由於這兩者都很相似,因此一併剖析。
  private void releaseWaiters(int phase) {
        QNode q;   // first element of queue
        Thread t;  // its thread
        AtomicReference<QNode> head = (phase & 1) == 0 ? evenQ : oddQ;
        while ((q = head.get()) != null &&
               q.phase != (int)(root.state >>> PHASE_SHIFT)) {
            if (head.compareAndSet(q, q.next) &&
                (t = q.thread) != null) {
                q.thread = null;
                LockSupport.unpark(t);
            }
        }
    }


    private int abortWait(int phase) {
        AtomicReference<QNode> head = (phase & 1) == 0 ? evenQ : oddQ;
        for (;;) {
            Thread t;
            QNode q = head.get();
            int p = (int)(root.state >>> PHASE_SHIFT);
            if (q == null || ((t = q.thread) != null && q.phase == p))
                return p;
            if (head.compareAndSet(q, q.next) && t != null) {
                q.thread = null;
                LockSupport.unpark(t);
            }
        }
    }
    releaseWaiters方法主要利用自旋從head結點起把隊列裏的結點出隊,如果結點的thread引用爲非null,則順便喚醒。另外注意的是,每次出隊前都會判斷當前結點的階段號是否與狀態的階段號相等,這裏的狀態階段號用的是root.state,這是考慮到堆疊的情況。
    abortWait的實現是releaseWaiters的變種,從頭結點開始,如果遇到沒有被取消等待(thread引用是否爲null)並且階段號與當前相等的正常阻塞結點,就會退出,否則一直釋放結點。

arrive實現
    Phaser提供了單獨表示到達階段的非阻塞函數,即
    int arrive()                    //一個party到達
    int arriveAndDeregister()       //一個party到達並且反註冊這個party
    這兩個函數的實現都很簡單:
    public int arrive() {
        return doArrive(ONE_ARRIVAL);
    }


    public int arriveAndDeregister() {
        return doArrive(ONE_DEREGISTER);
    }
    主要是調用doArrive實現,doArrive實現如下
  private int doArrive(int adjust) {
        final Phaser root = this.root;
        for (;;) {
            long s = (root == this) ? state : reconcileState();
            int phase = (int)(s >>> PHASE_SHIFT);
            if (phase < 0)
                return phase;
            int counts = (int)s;
            int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);
            if (unarrived <= 0)
                throw new IllegalStateException(badArrive(s));
            if (UNSAFE.compareAndSwapLong(this, stateOffset, s, s-=adjust)) {
                if (unarrived == 1) {
                    long n = s & PARTIES_MASK;  // base of next state
                    int nextUnarrived = (int)n >>> PARTIES_SHIFT;
                    if (root == this) {
                        if (onAdvance(phase, nextUnarrived))
                            n |= TERMINATION_BIT;
                        else if (nextUnarrived == 0)
                            n |= EMPTY;
                        else
                            n |= nextUnarrived;
                        int nextPhase = (phase + 1) & MAX_PHASE;
                        n |= (long)nextPhase << PHASE_SHIFT;
                        UNSAFE.compareAndSwapLong(this, stateOffset, s, n);
                        releaseWaiters(phase);
                    }
                    else if (nextUnarrived == 0) { // propagate deregistration
                        phase = parent.doArrive(ONE_DEREGISTER);
                        UNSAFE.compareAndSwapLong(this, stateOffset,
                                                  s, s | EMPTY);
                    }
                    else
                        phase = parent.doArrive(ONE_ARRIVAL);
                }
                return phase;
            }
        }
    }
    doArrive看上去很複雜,但其實邏輯並不算太複雜。
    1、首先依然是進入一個自旋結構,然後根據是否有堆疊(root == this)來獲取正確狀態值,接着計算階段號phase,未到達parties數unarrived,此時如果unarrived少於等於0,則必須拋出異常,表示這次到達是非法的(因爲所有的註冊parties數已經到達);
    2、接着就是利用CAS把狀態值進行更改,這裏是減去參數adjust值,arrive的傳入參數是ONE_ARRIVE,也就是1,arriveAndDeregister是ONE_ARRIVAL|ONE_PARTY,減去之後剛好把未到達數和註冊數都減去一。
    3、如果CAS成功,如果CAS之前的unarrived剛好爲1,則表示此次到達是最後一個未到達party,然後重新開始計算下一個階段值n,接着需要根據是否堆疊進行判斷:
    (1)如果沒有堆疊(root == this)則按照定義我們調用onAdvance,傳入相對參數,此時如果onAdvance返回true,我們給n添加終結標識,如果onAdvance返回false,但下階段的未到達parties數(同時也是當前註冊的parties數)爲0(可能由於反註冊造成),因此要給n添加EMPTY值,否則就給n添加新的未到達parties數,接着就調用CAS把當前狀態值更改爲n,然後調用releaseWaiters釋放上一階段號的等待隊列。注意這裏第二個CAS的返回值可以忽略,因爲這裏與doRegister的衝突已經由doRegister的unarrived判斷解決。
    (2)如果(1)判斷失敗,則出現了堆疊,另外如果此時新的未到達數爲0(所有之前的註冊parties數都被反註冊),根據堆疊的結構,我們必須向parent表示已經到達一個party並且反註冊自己,並且同時把當前狀態CAS爲EMPTY,同樣,這裏的CAS可以忽略返回值。
    (3)如果(1)(2)判斷都失敗,則只需要簡單地把向parent調用doArrive(ONE_ARRIVE)表示自己當前所有已經註冊的parties數都到達了,然後parent就會減去一個代表這個子phaser的到達parties數。

    這裏順便介紹一個比較方便的函數arriveAndAwaitAdvance,從名字上就可以看出,這個函數把arrive和awaitAdvance兩個效果都合成在一起。具體實現如下:
    public int arriveAndAwaitAdvance() {
        final Phaser root = this.root;
        for (;;) {
            long s = (root == this) ? state : reconcileState();
            int phase = (int)(s >>> PHASE_SHIFT);
            if (phase < 0)
                return phase;
            int counts = (int)s;
            int unarrived = (counts == EMPTY) ? 0 : (counts & UNARRIVED_MASK);
            if (unarrived <= 0)
                throw new IllegalStateException(badArrive(s));
            if (UNSAFE.compareAndSwapLong(this, stateOffset, s,
                                          s -= ONE_ARRIVAL)) {
                if (unarrived > 1)
                    return root.internalAwaitAdvance(phase, null);
                if (root != this)
                    return parent.arriveAndAwaitAdvance();
                long n = s & PARTIES_MASK;  // base of next state
                int nextUnarrived = (int)n >>> PARTIES_SHIFT;
                if (onAdvance(phase, nextUnarrived))
                    n |= TERMINATION_BIT;
                else if (nextUnarrived == 0)
                    n |= EMPTY;
                else
                    n |= nextUnarrived;
                int nextPhase = (phase + 1) & MAX_PHASE;
                n |= (long)nextPhase << PHASE_SHIFT;
                if (!UNSAFE.compareAndSwapLong(this, stateOffset, s, n))
                    return (int)(state >>> PHASE_SHIFT); // terminated
                releaseWaiters(phase);
                return nextPhase;
            }
        }
    }
    函數的大致結構和doArrive差不多,在CAS之後如果unarrived大於1,則需要調用根結點的internalAwaitAdvance進行阻塞等待直到階段號增長,如果unarrived小於等於1,則如果有堆疊發生(root != this)則調用父phaser的arriveAndAwaitAdvance,否則的話調用onAdvance,並且調用CAS把狀態更新,然後調用releaseWaiters把之前的階段等待隊列釋放。該函數對比起先調用arrive和awaitAdvance,更加方便並且由於減少了一些多餘的變量讀取和邏輯,速度更加快。

總結
    Phaser的同步模型於CountDownLatch、CyclicBarrier類似,但提供了更加靈活的同步支持,另外由於實現採用了無鎖算法,整個同步操作的實現變得更加複雜。考慮到高併發條件下的CAS競爭,也提供了不同機制去優化性能(堆疊,自旋等)。

發佈了36 篇原創文章 · 獲贊 4 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章