07 併發工具類CountDownLatch、CyclicBarrier、Semaphore使用及原始碼分析

我只是有點困呦發表於2020-10-10

在 JUC 下包含了一些常用的同步工具類,今天就來詳細介紹一下,CountDownLatch,CyclicBarrier,Semaphore 的使用方法以及它們之間的區別。

1 CountDownLatch

1.1 功能描述

CountDownLatch是一個同步工具類,用來協調多個執行緒之間的同步。它允許一個或多個執行緒一直等待,直到其他執行緒的操作執行完畢再執行

CountDownLatch能夠使一個執行緒在等待另外一些執行緒完成各自工作之後,再繼續執行。使用一個計數器進行實現。計數器初始值為執行緒的數量。當每一個執行緒完成自己任務後,計數器的值就會減一。當計數器的值為0時,表示所有的執行緒都已經完成任務,然後在CountDownLatch上等待的執行緒就可以恢復執行接下來的任務。

countdownlatch 提供了兩個方法,一個是 countDown,一個是 await,countdownlatch初始化的時候需要傳入一個整數,在這個整數倒數到 0之前,呼叫了 await 方法的程式都必須要等待,然後通過 countDown 來倒數。

1.2 簡單案例

public class CountDownLatchDemo {

    private static final CountDownLatch countDownLatch = new CountDownLatch(3);

    public static void main(String[] args) {
        try {
            System.out.println("main執行緒開始執行");
            for (int i = 0; i < 3; i++) {
                new Thread(new CountDownLatchThread(), String.valueOf(i)).start();
                TimeUnit.SECONDS.sleep(1);
            }
            // 阻塞,直到countDownLatch為0
            // main執行緒等待3個子執行緒都執行完成會被喚醒
            countDownLatch.await();
            System.out.println("main執行緒執行完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class CountDownLatchThread implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println("子執行緒" + Thread.currentThread().getName() + "開始執行");
                TimeUnit.SECONDS.sleep(10);
                // 當前執行緒呼叫此方法,則計數減一
                countDownLatch.countDown();
                System.out.println("子執行緒" + Thread.currentThread().getName() + "執行完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

程式功能:main執行緒需要等待其他3個子執行緒執行結束後,才會繼續執行後續的程式碼。有點類似 join 的功能,但是比 join 更加靈活。

join的功能:在當前執行緒中,如果呼叫某個thread的join方法,那麼當前執行緒就會被阻塞,直到thread執行緒執行完畢,當前執行緒才能繼續執行。join的原理是,不斷的檢查thread是否存活,如果存活,那麼讓當前執行緒一直wait,直到thread執行緒終止,執行緒的this.notifyAll 就會被呼叫。

與join的區別:假設子執行緒的工作都分為兩個階段,主執行緒只需要等待子執行緒完成第一個階段就可以執行了。使用join是無法實現的,此時使用countDownLatch改變countDown()的位置即可。

1.3 原始碼分析

1.3.1 類圖UML

image-20201009232803482

它的底層實現是基於 AQS 的共享鎖。

1.3.2 初始化

CountDownLatch 使用了共享鎖模式。CountDownLatch 使用了一個內部類 Sync來實現CountDownLatch的同步控制,而Sync是AQS的一個實現類,它使用AQS的狀態(state)來表示count。

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    Sync(int count) {
        setState(count);
    }

    int getCount() {
        return getState();
    }

    protected int tryAcquireShared(int acquires) {
        return (getState() == 0) ? 1 : -1;
    }

    protected boolean tryReleaseShared(int releases) {
        // Decrement count; signal when transition to zero
        for (;;) {
            int c = getState();
            if (c == 0)
                return false;
            int nextc = c-1;
            if (compareAndSetState(c, nextc))
                return nextc == 0;
        }
    }
}

1.3.3 await()

導致當前的執行緒等待直到count被倒數到0,或者執行緒被中斷。

public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 執行緒被中斷,丟擲中斷異常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 嘗試獲取共享鎖
    if (tryAcquireShared(arg) < 0)
      	// 獲取鎖失敗,加入等待佇列
        doAcquireSharedInterruptibly(arg);
}

tryAcquireShared只有當State為0才可以獲取到同步鎖

protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}

doAcquireSharedInterruptibly:獲取共享鎖失敗加入等待佇列(可中斷),程式碼同ReentrantReadWriteLock中的ReadLoack,詳見之前的程式碼分析。

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

1.3.4 countDown()

public void countDown() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    // 釋放共享鎖,當釋放後State為0。喚醒共享鎖的同步佇列中等待的執行緒。
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}
protected boolean tryReleaseShared(int releases) {
    // Decrement count; signal when transition to zero
    for (;;) {
        int c = getState();
        if (c == 0)
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

2 CyclicBarrier

2.1 功能描述

作用:讓所有執行緒都等待完成後才會繼續下一步行動。

柵欄類似於閉鎖,它能阻塞一組執行緒直到某個事件的發生。柵欄與閉鎖的關鍵區別在於,所有的執行緒必須同時到達柵欄位置,才能繼續執行。閉鎖用於等待事件,而柵欄用於等待其他執行緒。

CyclicBarrier可以使一定數量的執行緒反覆地在柵欄位置處彙集。當執行緒到達柵欄位置時將呼叫await方法,這個方法將阻塞直到所有執行緒都到達柵欄位置。如果所有執行緒都到達柵欄位置,那麼柵欄將開啟,此時所有的執行緒都將被釋放,而柵欄將被重置以便下次使用。

2.2 簡單案例

public class CyclicBarrierDemo {

    private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new BarrierAction());

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 4; i++) {
            new Thread(new BarrierThread(), String.valueOf(i)).start();
            TimeUnit.MILLISECONDS.sleep(1);
        }
    }

    static class BarrierThread implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println("執行緒: " + Thread.currentThread().getName() + " 啟動");
                TimeUnit.SECONDS.sleep(5);
                cyclicBarrier.await();
                System.out.println("執行緒: " + Thread.currentThread().getName() + " 結束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * CyclicBarrier可以再次使用
     */
    static class CyclicBarrierThread implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println("執行緒: " + Thread.currentThread().getName() + " 啟動");
                cyclicBarrier.await();
                System.out.println("執行緒: " + Thread.currentThread().getName() + " 結束");

                TimeUnit.SECONDS.sleep(10);
                System.out.println("執行緒: " + Thread.currentThread().getName() + " 再次啟動");
                cyclicBarrier.await();
                System.out.println("執行緒: " + Thread.currentThread().getName() + " 再次結束");
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    static class BarrierAction implements Runnable {
        @Override
        public void run() {
            System.out.println("柵欄都到達後,第一個先執行的執行緒");
        }
    }
}

執行緒: 0 啟動
執行緒: 1 啟動
執行緒: 2 啟動
執行緒: 3 啟動
柵欄都到達後,第一個先執行的執行緒
執行緒: 3 結束
執行緒: 0 結束
執行緒: 2 結束
執行緒: 1 結束

把parties改為2後的輸出結果是什麼呢?

先執行兩個後再執行另外兩個。分兩批執行。這個就是柵欄的迴圈使用

執行緒: 0 啟動
執行緒: 1 啟動
執行緒: 2 啟動
執行緒: 3 啟動
柵欄都到達後,第一個先執行的執行緒
執行緒: 1 結束
執行緒: 0 結束
柵欄都到達後,第一個先執行的執行緒
執行緒: 3 結束
執行緒: 2 結束

2.3 原始碼分析

image-20201009233245167

底層使用ReentrantLock 和 Condition來實現的。

2.3.1 初始化

/** The lock for guarding barrier entry */
// 可重入獨佔鎖
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
// 等待佇列
private final Condition trip = lock.newCondition();
/** The number of parties */
// 參與的執行緒數量
private final int parties;
/* The command to run when tripped */
// 由最後一個進入 barrier 的執行緒執行的操作
private final Runnable barrierCommand;
/** The current generation */
// 當前代
private Generation generation = new Generation();
// 正在等待進入屏障的執行緒數量
private int count;
private static class Generation {
    boolean broken = false;
}

CyclicBarrier中主要的成員變數。

  • CyclicBarrier 使用 ReentrantLock 和 Condition 類來構建。之前的原始碼分析已經分析了這兩個類,具體可以參考之前的文章。
  • CyclicBarrier 類存在一個內部類 Generation,每一次使用 CyclicBarrier 可以當成 Generation 的例項。
public CyclicBarrier(int parties) {
    this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
  	// 參與的執行緒數量小於等於0,丟擲異常
    if (parties <= 0) throw new IllegalArgumentException();
 		// 設定parties 
    this.parties = parties;
  	// 設定count
    this.count = parties;
  	// 設定barrierCommand
    this.barrierCommand = barrierAction;
}

CyclicBarrier有兩個建構函式

CyclicBarrier 預設的構造方法是 CyclicBarrier(int parties),其參數列示柵欄攔截的執行緒數量,每個執行緒呼叫 await 方法告訴 CyclicBarrier 當前執行緒已經到達了柵欄,然後當前執行緒被阻塞。

CyclicBarrier的另一個建構函式CyclicBarrier(int parties, Runnable barrierAction),用於執行緒到達柵欄時,優先執行barrierAction,方便處理更復雜的業務場景。由最後一個進入 barrier 的執行緒執行的操作。

2.3.2 await

呼叫await方法的執行緒告訴CyclicBarrier自己已經到達同步點,然後當前執行緒被阻塞,直到parties個參與執行緒呼叫了await方法。CyclicBarrier同樣提供帶超時時間的await和不帶超時時間的await方法。

超時時間的await,時間到達後。會先破壞柵欄,之後喚醒所有的執行緒。

public int await() throws InterruptedException, BrokenBarrierException {
    try {
      	// 不超時等待
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
        throw new Error(toe); // cannot happen
    }
}
public int await(long timeout, TimeUnit unit)
    throws InterruptedException,
           BrokenBarrierException,
           TimeoutException {
    return dowait(true, unit.toNanos(timeout));
}

這兩個方法最終都會呼叫dowait(boolean, long)方法,它也是CyclicBarrier的核心方法

2.3.3 dowait

dowait(boolean, long)方法的主要邏輯處理比較簡單,如果該執行緒不是最後一個呼叫await方法的執行緒,則它會一直處於等待狀態,除非發生以下情況:

  • 最後一個執行緒到達,即index == 0
  • 某個參與執行緒等待超時
  • 某個參與執行緒被中斷
  • 呼叫了CyclicBarrier的reset()方法。該方法會將屏障重置為初始狀態
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    // 獲取獨佔鎖         
    final ReentrantLock lock = this.lock;
    // 當前執行緒鎖定         
    lock.lock();
    try {
        // 當前代
        final Generation g = generation;
				// 如果這代損壞了,丟擲異常
        if (g.broken)
            throw new BrokenBarrierException();
				// 如果執行緒中斷了,丟擲異常
        if (Thread.interrupted()) {
          	// 將損壞狀態設定為true
            // 並通知其他阻塞在此柵欄上的執行緒
            breakBarrier();
            throw new InterruptedException();
        }
				// 減少正在等待進入柵欄的執行緒數量
      	// 正在等待進入柵欄的執行緒數量
        int index = --count;
      	// 正在等待進入柵欄的執行緒數量為0,所有執行緒都已經進入
        if (index == 0) {  // tripped
          	// 執行的動作標識
            boolean ranAction = false;
            try {
              	// 初始化時的執行動作
                final Runnable command = barrierCommand;
                // 執行動作不為空
                if (command != null)
                  	// 執行
                    command.run();
              	// 設定ranAction狀態
                ranAction = true;
                // 進入下一代,迴圈使用的重要原理
                nextGeneration();
                return 0;
            } finally {
              	// 上面發生異常
                if (!ranAction)
                  	// 損壞當前屏障
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
      	// 無限迴圈
        for (;;) {
            try {
              	// 沒有設定等待時間
                if (!timed)
                  	// 當前執行緒等待,加入等待佇列中
                    trip.await();
              	// 置等待時間
                else if (nanos > 0L)
                  	// 當前執行緒等待,加入等待佇列中。超時後當前執行緒自動喚醒
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) { // 執行緒等待await中被中斷
              	// 等於當前代並且柵欄沒有被損壞
                if (g == generation && ! g.broken) {
                  	// 損壞當前柵欄
                    breakBarrier();
                    // 丟擲異常
                    throw ie;
                } else {
                    // 上面條件不滿足,說明這個執行緒不是這代的
                    // 就不會影響當前這代柵欄的執行,所以,就打個中斷標記
                  	// 中斷當前執行緒
                    Thread.currentThread().interrupt();
                }
            }
						// 當有任何一個執行緒中斷了,就會呼叫breakBarrier方法
            // 就會喚醒其他的執行緒,其他執行緒醒來後,也要丟擲異常
            if (g.broken)
                throw new BrokenBarrierException();
						// 不等於當前代
          	// g != generation表示正常換代了,返回當前執行緒所在柵欄的下標
            // 如果 g == generation,說明還沒有換代,那為什麼會醒了?
            // 因為一個執行緒可以使用多個柵欄,當別的柵欄喚醒了這個執行緒,就會走到這裡,所以需要判斷是否是當前代。
            // 正是因為這個原因,才需要generation來保證正確。
            if (g != generation)
              	// 正在等待進入柵欄的執行緒數量
                return index;
						// 設定了等待時間,並且等待時間小於0
          	// await(long timeout, TimeUnit unit)
            // 柵欄破壞,喚醒所有await的執行緒
            if (timed && nanos <= 0L) {
              	// 損壞柵欄
                breakBarrier();
              	// 丟擲異常
                throw new TimeoutException();
            }
        }
    } finally {
      	// 釋放鎖
        lock.unlock();
    }
}

我們可能需要注意Generation 物件,在上述程式碼中我們總是可以看到丟擲BrokenBarrierException異常,那麼什麼時候丟擲異常呢?如果一個執行緒處於等待狀態時,如果其他執行緒呼叫reset(),或者呼叫的barrier原本就是被損壞的,則丟擲BrokenBarrierException異常。同時,任何執行緒在等待時被中斷了,則其他所有執行緒都將丟擲BrokenBarrierException異常,並將barrier置於損壞狀態。

同時,Generation描述著CyclicBarrier的更新換代。在CyclicBarrier中,同一批執行緒屬於同一代。當有parties個執行緒到達barrier之後,generation就會被更新換代。其中broken標識該當前CyclicBarrier是否已經處於中斷狀態。

2.3.4 breakBarrier

當barrier損壞了或者有一個執行緒中斷了,則通過breakBarrier()來終止所有的執行緒。

在breakBarrier()中除了將broken設定為true,還會呼叫signalAll將在CyclicBarrier處於等待狀態的執行緒全部喚醒。

private void breakBarrier() {
  	// 設定狀態
    generation.broken = true;
  	// 恢復正在等待進入柵欄的執行緒數量
    count = parties;
  	// 喚醒所有執行緒
    trip.signalAll();
}

2.3.5 nextGeneration

當所有執行緒都已經到達barrier處(index == 0),則會通過nextGeneration()進行更新換地操作,在這個步驟中,做了三件事:喚醒所有執行緒,重置count,generation

private void nextGeneration() {
    // signal completion of last generation
  	// 喚醒所有執行緒
    trip.signalAll();
    // set up next generation
  	// 重置正在等待進入屏障的執行緒數量
    count = parties;
    // 新生一代
    generation = new Generation();
}

除了上面講到的柵欄更新換代以及損壞狀態,我們在使用CyclicBarrier時還要要注意以下幾點:

  • CyclicBarrier使用獨佔鎖來執行await方法,併發性可能不是很高
  • 如果在等待過程中,執行緒被中斷了,就丟擲異常。但如果中斷的執行緒所對應的CyclicBarrier不是這代的,比如,在最後一次執行緒執行signalAll後,並且更新了這個“代”物件。在這個區間,這個執行緒被中斷了,那麼,JDK認為任務已經完成了,就不必在乎中斷了,只需要打個標記。該部分原始碼已在dowait(boolean, long)方法中進行了註釋。
  • 如果執行緒被其他的CyclicBarrier喚醒了,那麼g肯定等於generation,這個事件就不能return了,而是繼續迴圈阻塞。反之,如果是當前CyclicBarrier喚醒的,就返回執行緒在CyclicBarrier的下標。完成了一次衝過柵欄的過程。該部分原始碼已在dowait(boolean, long)方法中進行了註釋。

3 Semaphore

3.1 功能描述

semaphore 也就是我們常說的訊號燈,semaphore 可以控制同時訪問的執行緒個數,通過 acquire 獲取一個許可,如果沒有就等待,通過 release 釋放一個許可。有點類似限流的作用。叫訊號燈的原因也和他的用處有關,比如某商場就 5 個停車位,每個停車位只能停一輛車,如果這個時候來了 10 輛車,必須要等前面有空的車位才能進入。

3.2 簡單案例

public class SemaphoreDemo {

    private static Semaphore semaphore = new Semaphore(5);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(new ParkCar(), "車輛 " + i).start();
            TimeUnit.MILLISECONDS.sleep(100);
        }
    }

    static class ParkCar implements Runnable {
        @Override
        public void run() {
            try {
                // 申請許可證類似於檢視有無車位
                semaphore.acquire();
                System.out.println(Thread.currentThread().getName() + " 開始停車");
                // 模擬停車10s中
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 結束停車");
            // 釋放許可證類似於車子駛離停車場,空出來了車位
            semaphore.release();
        }
    }
}

輸出結果:
一次聽了5輛車,當後面車子到來時,停車場無空的位子,需要等待前面的車輛駛離停車場,駛離一輛,後面就可以加入一輛車子。

車位一共就發 5 個,那等第一批車輛用完釋放之後, 第二批的時候應該發給誰呢?

所有等待的車輛都想先拿到許可,先通行,怎麼辦。這就需要,用到鎖了。就所有人都去搶,誰先搶到,誰就先停車。

車輛 0 開始停車
車輛 1 開始停車
車輛 2 開始停車
車輛 3 開始停車
車輛 4 開始停車
車輛 0 結束停車
車輛 5 開始停車
車輛 1 結束停車
車輛 6 開始停車
車輛 2 結束停車
車輛 7 開始停車
車輛 3 結束停車
車輛 8 開始停車
車輛 4 結束停車
車輛 9 開始停車
車輛 5 結束停車
車輛 6 結束停車
車輛 7 結束停車
車輛 8 結束停車
車輛 9 結束停車

3.3 原始碼分析

3.3.1 UML類圖

WX20201009-232147

它的底層實現是基於 AQS 的共享鎖。

3.3.2 初始化

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

從構造器裡面可以看出來semaphore預設實現的是非公平鎖,當然我們也可以指定鎖的型別,是否為公平鎖。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    NonfairSync(int permits) {
        super(permits);
    }

    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

Sync(int permits) {
    setState(permits);
}

protected final void setState(int newState) {
     state = newState;
}

我們可以看到NonfairSync類繼承了Sync,而Sync繼承了AQS,從這裡其實可以看出來semaphore是基於AQS實現的。AQS中的同步狀態值state儲存許可證的個數

以下程式碼以非公平鎖為例

3.3.3 acquire

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
  	// 如果執行緒中斷則丟擲異常
    if (Thread.interrupted())
        throw new InterruptedException();
    // 申請共享鎖
    if (tryAcquireShared(arg) < 0)
        // 申請共享鎖失敗,加入等待佇列
        doAcquireSharedInterruptibly(arg);
}

3.3.4 tryAcquireShared

protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int acquires) {
    return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
  	// 自旋
    for (;;) {
      	// 獲取許可數
        int available = getState();
        // 剩餘許可數
        int remaining = available - acquires;
        // 剩餘許可數大於0,當前執行緒可以執行。CAS修改許可數
        // 剩餘許可數小於0,加入同步佇列等待。
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

3.3.5 doAcquireSharedInterruptibly

許可數用完,加入同步佇列。等待許可的釋放。

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

前面分析ReentrantReadWriteLock.ReadLock詳細分析過。

3.3.6 release

public void release() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

此處sync呼叫了AQS中的方法releaseShared,在這個方法中如果釋放成功那麼就呼叫doReleaseShared方法,此方法在前面AQS共享模式一文中已經講解過,此處不在詳細講解。它主要作用就是釋放佇列中的節點。

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
      	// 獲取當前許可數
        int current = getState();
        // 釋放後的許可數
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        // 更新新的許可數
        if (compareAndSetState(current, next))
            return true;
    }
}
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

相關文章