Java并發(fā)編程之并發(fā)工具類CountDownLatch,CyclicBarrier,Semaphore詳解

1 前言

在JDK的并發(fā)包里提供了幾個(gè)非常有用的并發(fā)工具類。CountDownLatch、CyclicBarrier和 Semaphore工具類提供了一種并發(fā)流程控制的手段,Exchanger工具類則提供了在線程間交換數(shù) 據(jù)的一種手段。本文會(huì)對(duì)這些并發(fā)工具類進(jìn)行介紹。

2 等待多線程完成的CountDownLatch

2.1 CountDownLatch案例演示

countdownlatch 是一個(gè)同步工具類,它允許一個(gè)或多個(gè)線程一直等待,直到其他線程的操作執(zhí)行完畢再執(zhí)行。從命名可以解讀到 countdown 是倒數(shù)的意思,類似于我們倒計(jì)時(shí)的概念。
countdownlatch 提供了兩個(gè)方法,一個(gè)是 countDown,一個(gè)是 await,countdownlatch 初始化的時(shí)候需要傳入一個(gè)整數(shù),在這個(gè)整數(shù)倒數(shù)到 0 之前,調(diào)用了 await 方法的程序都必須要等待,然后通過(guò) countDown 來(lái)倒數(shù)。
現(xiàn)在有一個(gè)需求就是:學(xué)校放學(xué)了,等學(xué)生們?nèi)孔咄曛笤訇P(guān)閉教室的門,如果不用CountDownLatch的話代碼如下:

public class WithoutCountDownLatchDemo {
    public static void main(String[] args) {
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "同學(xué)離開(kāi)了");
            }).start();
        }
        System.out.println(Thread.currentThread().getName() + "要關(guān)門了,此時(shí)教室已經(jīng)沒(méi)有人了~");
    }
}

輸出結(jié)果如下:

Thread-0同學(xué)離開(kāi)了
main要關(guān)門了,此時(shí)教室已經(jīng)沒(méi)有人了~
Thread-1同學(xué)離開(kāi)了
Thread-2同學(xué)離開(kāi)了
Thread-3同學(xué)離開(kāi)了
Thread-4同學(xué)離開(kāi)了
Thread-5同學(xué)離開(kāi)了

同學(xué)還沒(méi)有走光,但是門卻先關(guān)了,這樣顯然是不對(duì)的,那么使用了CountDownLatch的話代碼是怎樣的呢?

public class CountDownLatchDemo {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"同學(xué)離開(kāi)了");
                countDownLatch.countDown();
            }).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"要關(guān)門了,此時(shí)教室已經(jīng)沒(méi)人了~");
    }
}

運(yùn)行結(jié)果如下:

Thread-0同學(xué)離開(kāi)了
Thread-2同學(xué)離開(kāi)了
Thread-1同學(xué)離開(kāi)了
Thread-3同學(xué)離開(kāi)了
Thread-5同學(xué)離開(kāi)了
Thread-4同學(xué)離開(kāi)了
main要關(guān)門了,此時(shí)教室已經(jīng)沒(méi)人了~

等到同學(xué)們?nèi)孔咄曛螅砰_(kāi)始關(guān)門,這樣才是正確的!
從如下代碼中:

public class CountDownLatchDemo2 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch=new CountDownLatch(3);
        new Thread(() -> {
            System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行中");
            countDownLatch.countDown();
            System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行完畢");
        },"t1").start();
        new Thread(()->{
            System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行中");
            countDownLatch.countDown();
            System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行完畢");
        },"t2").start();
        new Thread(()->{
            System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行中");
            countDownLatch.countDown();
            System.out.println(""+Thread.currentThread().getName()+"-執(zhí)行完畢");
        },"t3").start();
        countDownLatch.await();
        System.out.println("所有線程執(zhí)行完畢");
    }
}

可以看出有點(diǎn)類似 join 的功能,但是比 join 更加靈活。CountDownLatch 構(gòu)造函數(shù)會(huì)接收一個(gè) int 類型的參數(shù)作為計(jì)數(shù)器的初始值,當(dāng)調(diào)用 CountDownLatch 的countDown 方法時(shí),這個(gè)計(jì)數(shù)器就會(huì)減一。通過(guò) await 方法去阻塞去阻塞主流程。


流程圖

2.2 CountDownLatch源碼分析

2.2.1 CountDownLatch類圖

CountDownLatch繼承圖

對(duì)于 CountDownLatch,我們僅僅需要關(guān)心兩個(gè)方法,一個(gè)是 countDown() 方法,另一個(gè)是 await() 方法。countDown() 方法每次調(diào)用都會(huì)將 state 減 1,直到state 的值為 0;而 await 是一個(gè)阻塞方法,當(dāng) state 減 為 0 的時(shí)候,await 方法才會(huì)返回。await 可以被多個(gè)線程調(diào)用,大家在這個(gè)時(shí)候腦子里要有個(gè)圖:所有調(diào)用了await 方法的線程阻塞在 AQS 的阻塞隊(duì)列中,等待條件滿(state == 0),將線程從隊(duì)列中一個(gè)個(gè)喚醒過(guò)來(lái)。

2.2.2 acquireSharedInterruptibly

countdownlatch 也用到了 AQS,在 CountDownLatch 內(nèi)部寫了一個(gè) Sync 并且繼承了 AQS 這個(gè)抽象類重寫了 AQS中的共享鎖方法。首先看到下面這個(gè)代碼,這塊代碼主要是判斷當(dāng)前線程是否獲取到了共享鎖 ; ( 在CountDownLatch 中 , 使 用 的 是 共 享 鎖 機(jī) 制 ,因?yàn)镃ountDownLatch 并不需要實(shí)現(xiàn)互斥的特性)

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //state 如果不等于 0,說(shuō)明當(dāng)前線程需要加入到共享鎖隊(duì)列中
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

2.2.3 doAcquireSharedInterruptibly

  1. addWaiter 設(shè)置為 shared 模式
  2. tryAcquire 和 tryAcquireShared 的返回值不同,因此會(huì)多出一個(gè)判斷過(guò)程
  3. 在 判 斷 前 驅(qū) 節(jié) 點(diǎn) 是 頭 節(jié) 點(diǎn) 后 , 調(diào) 用 了setHeadAndPropagate 方法,而不是簡(jiǎn)單的更新一下頭節(jié)點(diǎn)。
    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        //創(chuàng)建一個(gè)共享模式的節(jié)點(diǎn)添加到隊(duì)列中
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    // 就判斷嘗試獲取鎖
                    int r = tryAcquireShared(arg);
                    //r>=0 表示獲取到了執(zhí)行權(quán)限,這個(gè)時(shí)候因?yàn)?state!=0,所以不會(huì)執(zhí)行這段代碼
                    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);
        }
    }

2.2.4 圖解分析

加入這個(gè)時(shí)候有 3 個(gè)線程調(diào)用了 await 方法,由于這個(gè)時(shí)候 state 的值還不為 0,所以這三個(gè)線程都會(huì)加入到 AQS隊(duì)列中。并且三個(gè)線程都處于阻塞狀態(tài)。


圖解分析

2.2.5 CountDownLatch.countDown

由于線程被 await 方法阻塞了,所以只有等到countdown 方法使得 state=0 的時(shí)候才會(huì)被喚醒,我們來(lái)看看 countdown 做了什么

  1. 只有當(dāng) state 減為 0 的時(shí)候,tryReleaseShared 才返回 true, 否則只是簡(jiǎn)單的 state = state - 1
  2. 如果 state=0, 則調(diào)用 doReleaseShared 喚醒處于 await 狀態(tài)下的線程
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

CountDownLatch中的tryReleaseShared

        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;
            }
        }
    }

AQS.doReleaseShared
共享鎖的釋放和獨(dú)占鎖的釋放有一定的差別,前面喚醒鎖的邏輯和獨(dú)占鎖是一樣,先判斷頭結(jié)點(diǎn)是不是SIGNAL 狀態(tài),如果是,則修改為 0,并且喚醒頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)。
PROPAGATE: 標(biāo)識(shí)為 PROPAGATE 狀態(tài)的節(jié)點(diǎn),是共享鎖模式下的節(jié)點(diǎn)狀態(tài),處于這個(gè)狀態(tài)下的節(jié)點(diǎn),會(huì)對(duì)線程的喚醒進(jìn)行傳播。

    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        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);
                }
                // 這個(gè) CAS 失敗的場(chǎng)景是:執(zhí)行到這里的時(shí)候,剛好有一個(gè)節(jié)點(diǎn)入隊(duì),入隊(duì)會(huì)將這個(gè) ws 設(shè)置為 -1
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            // 如果到這里的時(shí)候,前面喚醒的線程已經(jīng)占領(lǐng)了 head,那么再循環(huán)
            // 通過(guò)檢查頭節(jié)點(diǎn)是否改變了,如果改變了就繼續(xù)循環(huán)
            if (h == head)                   // loop if head changed
                break;
        }
    }

h == head:說(shuō)明頭節(jié)點(diǎn)還沒(méi)有被剛剛用unparkSuccessor 喚醒的線程(這里可以理解為ThreadB)占有,此時(shí) break 退出循環(huán)。
h != head:頭節(jié)點(diǎn)被剛剛喚醒的線程(這里可以理解為ThreadB)占有,那么這里重新進(jìn)入下一輪循環(huán),喚醒下一個(gè)節(jié)點(diǎn)(這里是 ThreadB )。我們知道,等到ThreadB 被喚醒后,其實(shí)是會(huì)主動(dòng)喚醒 ThreadC...
doAcquireSharedInterruptibly
一旦 ThreadA 被喚醒,代碼又會(huì)繼續(xù)回到doAcquireSharedInterruptibly 中來(lái)執(zhí)行。如果當(dāng)前 state滿足=0 的條件,則會(huì)執(zhí)行 setHeadAndPropagate 方法

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);
        //創(chuàng)建一個(gè)共享模式的節(jié)點(diǎn)添加到隊(duì)列中
        boolean failed = true;
        try {
            for (;;) {//被喚醒的線程進(jìn)入下一次循環(huán)繼續(xù)判斷
                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);
        }
    }

setHeadAndPropagate
這個(gè)方法的主要作用是把被喚醒的節(jié)點(diǎn),設(shè)置成 head 節(jié) 點(diǎn)。 然后繼續(xù)喚醒隊(duì)列中的其他線程。由于現(xiàn)在隊(duì)列中有 3 個(gè)線程處于阻塞狀態(tài),一旦 ThreadA被喚醒,并且設(shè)置為 head 之后,會(huì)繼續(xù)喚醒后續(xù)的ThreadB

   private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

圖解分析

圖解分析

3 同步屏障CyclicBarrier

CyclicBarrier的字面意思是可循環(huán)使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一 組線程到達(dá)一個(gè)屏障(也可以叫同步點(diǎn))時(shí)被阻塞,直到最后一個(gè)線程到達(dá)屏障時(shí),屏障才會(huì) 開(kāi)門,所有被屏障攔截的線程才會(huì)繼續(xù)運(yùn)行。

3.1 CyclicBarrier使用場(chǎng)景

使用場(chǎng)景:5個(gè)工程師一起來(lái)公司應(yīng)聘,招聘方式分為筆試和面試。首先,要等人到齊后,開(kāi)始筆試;筆試結(jié)束之后,再一起參加面試。把5個(gè)人看作5個(gè)線程,代碼如下:
Main類:

public class Main {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(5);
        for (int i = 0; i < 5; i++) {
            new MyThread("線程-" + (i + 1), barrier).start();
        }
    }
}

MyThread類:

public class MyThread extends Thread{

    private final CyclicBarrier barrier;
    private final Random random = new Random();
    public MyThread(String name, CyclicBarrier barrier) {
        super(name);
        this.barrier = barrier;
    }
    @Override public void run() {
        try {
            Thread.sleep(random.nextInt(2000));
            System.out.println(Thread.currentThread().getName() + " - 已經(jīng)到達(dá)公司");
            barrier.await();
            Thread.sleep(random.nextInt(2000));
            System.out.println(Thread.currentThread().getName() + " - 已經(jīng)筆試結(jié)束");
            barrier.await();
            Thread.sleep(random.nextInt(2000));
            System.out.println(Thread.currentThread().getName() + " - 已經(jīng)面試結(jié)束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
        super.run();
    }
}

在整個(gè)過(guò)程中,有2個(gè)同步點(diǎn):第1個(gè)同步點(diǎn),要等所有應(yīng)聘者都到達(dá)公司,再一起開(kāi)始筆試;第2個(gè)同步點(diǎn),要等所有應(yīng)聘者都結(jié)束筆試,之后一起進(jìn)入面試環(huán)節(jié)。

3.2 CyclicBarrier實(shí)現(xiàn)原理

CyclicBarrier基于ReentrantLock+Condition實(shí)現(xiàn)。

public class CyclicBarrier { 
    private final ReentrantLock lock = new ReentrantLock(); 
    // 用于線程之間相互喚醒 
    private final Condition trip = lock.newCondition(); 
    // 線程總數(shù) 
    private final int parties; 
    private int count; 
    private Generation generation = new Generation(); 
    // ... 
}

下面詳細(xì)介紹 CyclicBarrier 的實(shí)現(xiàn)原理。先看構(gòu)造方法:

    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        // 參與方數(shù)量
        this.parties = parties;
        this.count = parties;
        // 當(dāng)所有線程被喚醒時(shí),執(zhí)行barrierCommand表示的Runnable。
        this.barrierCommand = barrierAction;
    }

接下來(lái)看一下await()方法的實(shí)現(xiàn)過(guò)程。

    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }

 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();
            // 響應(yīng)中斷
            if (Thread.interrupted()) {
                // 喚醒所有阻塞的線程
                breakBarrier();
                throw new InterruptedException();
            }
            // 每個(gè)線程調(diào)用一次await(),count都要減1
            int index = --count;
            // 當(dāng)count減到0的時(shí)候,此線程喚醒其他所有線程
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    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) {
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                        Thread.currentThread().interrupt();
                    }
                }

                if (g.broken)
                    throw new BrokenBarrierException();

                if (g != generation)
                    return index;

                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
            lock.unlock();
        }
    }

    private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();
    }

    private void nextGeneration() {
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties;
        generation = new Generation();
    }

以上幾點(diǎn)的說(shuō)明:

  1. CyclicBarrier是可以被重用的。以上一節(jié)的應(yīng)聘場(chǎng)景為例,來(lái)了5個(gè)線程,這5個(gè)線程互相等待,到齊后一起被喚醒,各自執(zhí)行接下來(lái)的邏輯;然后,這5個(gè)線程繼續(xù)互相等待,到齊后再一起被喚醒。每一輪被稱為一個(gè)Generation,就是一次同步點(diǎn)。
  2. CyclicBarrier 會(huì)響應(yīng)中斷。5 個(gè)線程沒(méi)有到齊,如果有線程收到了中斷信號(hào),所有阻塞的線程也會(huì)被喚醒,就是上面的breakBarrier()方法。然后count被重置為初始值(parties),重新開(kāi)始。
  3. 上面的回調(diào)方法,barrierAction只會(huì)被第5個(gè)線程執(zhí)行1次(在喚醒其他4個(gè)線程之前),而不是5個(gè)線程每個(gè)都執(zhí)行1次。

3.3 CyclicBarrier與CountDownLatch 區(qū)別

CountDownLatch 是一次性的,CyclicBarrier 是可循環(huán)利用的
CountDownLatch的計(jì)數(shù)器只能使用一次,而CyclicBarrier的計(jì)數(shù)器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能夠處理更為復(fù)雜的場(chǎng)景;

4 控制并發(fā)線程數(shù)的Semaphore

Semaphore(信號(hào)量)是用來(lái)控制同時(shí)訪問(wèn)特定資源的線程數(shù)量,它通過(guò)協(xié)調(diào)各個(gè)線程,以 保證合理的使用公共資源

4.1 Semaphore的使用場(chǎng)景

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore=new Semaphore(3);//此時(shí)海底撈有3個(gè)空桌
        for (int i = 0; i < 6; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println("第"+Thread.currentThread().getName()+"等待者搶到座位。");
                    //假設(shè)每桌客人吃飯時(shí)間為3S
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println("第"+Thread.currentThread().getName()+"客人吃完飯離開(kāi)。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

運(yùn)行結(jié)果如下:

第0等待者搶到座位。
第1等待者搶到座位。
第2等待者搶到座位。
第1客人吃完飯離開(kāi)。
第0客人吃完飯離開(kāi)。
第2客人吃完飯離開(kāi)。
第4等待者搶到座位。
第5等待者搶到座位。
第3等待者搶到座位。
第5客人吃完飯離開(kāi)。
第3客人吃完飯離開(kāi)。
第4客人吃完飯離開(kāi)。

4.2 Semaphore源碼分析

假設(shè)有n個(gè)線程來(lái)獲取Semaphore里面的10份資源(n > 10),n個(gè)線程中只有10個(gè)線程能獲取到,其他線程都會(huì)阻塞。直到有線程釋放了資源,其他線程才能獲取到。


示例

當(dāng)初始的資源個(gè)數(shù)為1的時(shí)候,Semaphore退化為排他鎖。正因?yàn)槿绱耍琒emaphone的實(shí)現(xiàn)原理和鎖十分類似,是基于AQS,有公平和非公平之分。Semaphore相關(guān)類的繼承體系如下圖所示:


繼承體系

創(chuàng)建 Semaphore 實(shí)例的時(shí)候,需要一個(gè)參數(shù) permits,這個(gè)基本上可以確定是設(shè)置給 AQS 的 state 的,然后每個(gè)線程調(diào)用 acquire 的時(shí)候,執(zhí)行 state = state - 1,release 的時(shí)候執(zhí)行 state = state + 1,當(dāng)然,acquire 的時(shí)候,如果 state = 0,說(shuō)明沒(méi)有資源了,需要等待其他線程 release。
Semaphore 分公平策略和非公平策略

4.2.1 FairSync

   /**
     * Fair version
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

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

        protected int tryAcquireShared(int acquires) {
            for (;;) {
                // 區(qū)別就在于是不是會(huì)先判斷是否有線程在排隊(duì),然后才進(jìn)行 CAS 減操作
                if (hasQueuedPredecessors())
                    return -1;
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

4.2.2 NofairSync

通過(guò)對(duì)比發(fā)現(xiàn)公平和非公平的區(qū)別就在于是否多了一個(gè)hasQueuedPredecessors 的判斷

   /**
     * NonFair version
     */
    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);
        }
    }

    final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

后面的代碼和 CountDownLatch 的是完全一樣,都是基于共享鎖的實(shí)現(xiàn)。

5 線程間交換數(shù)據(jù)的Exchanger

Exchanger(交換者)是一個(gè)用于線程間協(xié)作的工具類。Exchanger用于進(jìn)行線程間的數(shù)據(jù)交 換。它提供一個(gè)同步點(diǎn),在這個(gè)同步點(diǎn),兩個(gè)線程可以交換彼此的數(shù)據(jù)。這兩個(gè)線程通過(guò) exchange方法交換數(shù)據(jù),如果第一個(gè)線程先執(zhí)行exchange()方法,它會(huì)一直等待第二個(gè)線程也 執(zhí)行exchange方法,當(dāng)兩個(gè)線程都到達(dá)同步點(diǎn)時(shí),這兩個(gè)線程就可以交換數(shù)據(jù),將本線程生產(chǎn) 出來(lái)的數(shù)據(jù)傳遞給對(duì)方。

5.1 使用場(chǎng)景

Exchanger用于線程之間交換數(shù)據(jù),代碼如下:

public class ExchangerDemo {
    private static final Random random = new Random();

    public static void main(String[] args) {
        // 建一個(gè)多線程共用的exchange對(duì)象
        // 把exchange對(duì)象傳給3個(gè)線程對(duì)象。每個(gè)線程在自己的run方法中調(diào)用exchange,把自 己的數(shù)據(jù)作為參數(shù)
        // 傳遞進(jìn)去,返回值是另外一個(gè)線程調(diào)用exchange傳進(jìn)去的參數(shù)
        Exchanger<String> exchanger = new Exchanger<>();

        new Thread("線程1") {
            @Override
            public void run() {
                while (true) {
                    try {
                        // 如果沒(méi)有其他線程調(diào)用exchange,線程阻塞,直到有其他線程調(diào) 用exchange為止。
                        String otherData = exchanger.exchange("交換數(shù)據(jù)1");
                        System.out.println(Thread.currentThread().getName() + "得到<==" + otherData);
                        Thread.sleep(random.nextInt(2000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();


        new Thread("線程2") {
            @Override
            public void run() {
                while (true) {
                    try {
                        String otherData = exchanger.exchange("交換數(shù)據(jù)2");
                        System.out.println(Thread.currentThread().getName() + "得到<==" + otherData);
                        Thread.sleep(random.nextInt(2000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread("線程3") {
            @Override
            public void run() {
                while (true) {
                    try {
                        String otherData = exchanger.exchange("交換數(shù)據(jù)3");
                        System.out.println(Thread.currentThread().getName() + "得到<==" + otherData);
                        Thread.sleep(random.nextInt(2000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

在上面的例子中,3個(gè)線程并發(fā)地調(diào)用exchange(...),會(huì)兩兩交互數(shù)據(jù),如1/2、1/3和2/3。

5.2 Exchanger實(shí)現(xiàn)原理

Exchanger的核心機(jī)制和Lock一樣,也是CAS+park/unpark。

5.2.1 Exchanger內(nèi)部代碼

首先,在Exchanger內(nèi)部,有兩個(gè)內(nèi)部類:Participant和Node,代碼如下:

public class Exchanger<V> {
    //...
    // 添加了Contended注解,表示偽共享與緩存行填充
    @sun.misc.Contended static final class Node {
        int index;              // Arena index
        int bound;              // Last recorded value of Exchanger.bound
        int collides;           // 本次綁定中,CAS操作失敗次數(shù)
        int hash;               // 自旋偽隨機(jī)
        Object item;            // 本線程要交換的數(shù)據(jù)
        volatile Object match;  //  對(duì)方線程交換來(lái)的數(shù)據(jù)
        // 當(dāng)前線程
        volatile Thread parked; // 當(dāng)前線程阻塞的時(shí)候設(shè)置該屬性,不阻塞為null。
    }

    /** The corresponding thread local class */
    static final class Participant extends ThreadLocal<java.util.concurrent.Exchanger.Node> {
        public java.util.concurrent.Exchanger.Node initialValue() { return new java.util.concurrent.Exchanger.Node(); }
    }
    //...
}

每個(gè)線程在調(diào)用exchange(...)方法交換數(shù)據(jù)的時(shí)候,會(huì)先創(chuàng)建一個(gè)Node對(duì)象。
這個(gè)Node對(duì)象就是對(duì)該線程的包裝,里面包含了3個(gè)重要字段:第一個(gè)是該線程要交互的數(shù)據(jù),第二個(gè)是對(duì)方線程交換來(lái)的數(shù)據(jù),最后一個(gè)是該線程自身。
一個(gè)Node只能支持2個(gè)線程之間交換數(shù)據(jù),要實(shí)現(xiàn)多個(gè)線程并行地交換數(shù)據(jù),需要多個(gè)Node,因此在Exchanger里面定義了Node數(shù)組:

    /**
     * Elimination array; null until enabled (within slotExchange).
     * Element accesses use emulation of volatile gets and CAS.
     */
    private volatile Node[] arena;

5.2.2 exchange(V x)實(shí)現(xiàn)分析

明白了大致思路,下面來(lái)看exchange(V x)方法的詳細(xì)實(shí)現(xiàn):

    @SuppressWarnings("unchecked")
    public V exchange(V x) throws InterruptedException {
        Object v;
        Object item = (x == null) ? NULL_ITEM : x; // translate null args
        if ((arena != null ||
             (v = slotExchange(item, false, 0L)) == null) &&
            ((Thread.interrupted() || // disambiguates null return
              (v = arenaExchange(item, false, 0L)) == null)))
            throw new InterruptedException();
        return (v == NULL_ITEM) ? null : (V)v;
    }

上面方法中,如果arena不是null,表示啟用了arena方式交換數(shù)據(jù)。如果arena不是null,并且線程被中斷,則拋異常
如果arena不是null,并且arenaExchange的返回值為null,則拋異常。對(duì)方線程交換來(lái)的null值是封裝為NULL_ITEM對(duì)象的,而不是null。
如果slotExchange的返回值是null,并且線程被中斷,則拋異常。
如果slotExchange的返回值是null,并且areaExchange的返回值是null,則拋異常。

slotExchange的實(shí)現(xiàn):

    /**
     * Exchange function used until arenas enabled. See above for explanation.
     * 如果不啟用arenas,則使用該方法進(jìn)行線程間數(shù)據(jù)交換。
     * @param item 需要交換的數(shù)據(jù)
     * @param timed 是否是計(jì)時(shí)等待,true表示是計(jì)時(shí)等待
     * @param ns  如果是計(jì)時(shí)等待,該值表示最大等待的時(shí)長(zhǎng)。
     * @return 對(duì)方線程交換來(lái)的數(shù)據(jù);如果等待超時(shí)或線程中斷,或者啟用了arena,則返回 null。
     */
    private final Object slotExchange(Object item, boolean timed, long ns) {
        // participant在初始化的時(shí)候設(shè)置初始值為new Node()
        // 獲取本線程要交換的數(shù)據(jù)節(jié)點(diǎn)
        Node p = participant.get();
        // 獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // 如果線程被中斷,則返回null。
        if (t.isInterrupted()) // preserve interrupt status so caller can recheck
            return null;

        for (Node q;;) {
            // 如果slot非空,表明有其他線程在等待該線程交換數(shù)據(jù)
            if ((q = slot) != null) {
                // CAS操作,將當(dāng)前線程的slot由slot設(shè)置為null
                // 如果操作成功,則執(zhí)行if中的語(yǔ)句
                if (U.compareAndSwapObject(this, SLOT, q, null)) {
                    // 獲取對(duì)方線程交換來(lái)的數(shù)據(jù)
                    Object v = q.item;
                    // 設(shè)置要交換的數(shù)據(jù)
                    q.match = item;
                    // 獲取q中阻塞的線程對(duì)象
                    Thread w = q.parked;
                    if (w != null)
                        // 如果對(duì)方阻塞的線程非空,則喚醒阻塞的線程
                        U.unpark(w);
                    return v;
                }
                // create arena on contention, but continue until slot null
                // 創(chuàng)建arena用于處理多個(gè)線程需要交換數(shù)據(jù)的場(chǎng)合,防止slot沖突
                if (NCPU > 1 && bound == 0 &&
                    U.compareAndSwapInt(this, BOUND, 0, SEQ))
                    arena = new Node[(FULL + 2) << ASHIFT];
            }
            // 如果arena不是null,需要調(diào)用者調(diào)用arenaExchange方法接著獲取對(duì)方線程交 換來(lái)的數(shù)據(jù)
            else if (arena != null)
                return null; // caller must reroute to arenaExchange
            else {
                // 如果slot為null,表示對(duì)方?jīng)]有線程等待該線程交換數(shù)據(jù)
                // 設(shè)置要交換的本方數(shù)據(jù)
                p.item = item;
                // 設(shè)置當(dāng)前線程要交換的數(shù)據(jù)到slot
                // CAS操作,如果設(shè)置失敗,則進(jìn)入下一輪for循環(huán)
                if (U.compareAndSwapObject(this, SLOT, null, p))
                    break;
                p.item = null;
            }
        }

        // await release
        // 沒(méi)有對(duì)方線程等待交換數(shù)據(jù),將當(dāng)前線程要交換的數(shù)據(jù)放到slot中,是一個(gè)Node對(duì)象
        // 然后阻塞,等待喚醒
        int h = p.hash;
        // 如果是計(jì)時(shí)等待交換,則計(jì)算超時(shí)時(shí)間;否則設(shè)置為0。
        long end = timed ? System.nanoTime() + ns : 0L;
        // 如果CPU核心數(shù)大于1,則使用SPINS數(shù),自旋;否則為1,沒(méi)必要自旋。
        int spins = (NCPU > 1) ? SPINS : 1;
        // 記錄對(duì)方線程交換來(lái)的數(shù)據(jù)
        Object v;
        // 如果p.match==null,表示還沒(méi)有線程交換來(lái)數(shù)據(jù)
        while ((v = p.match) == null) {
            // 如果自旋次數(shù)大于0,計(jì)算hash隨機(jī)數(shù)
            if (spins > 0) {
                // 生成隨機(jī)數(shù),用于自旋次數(shù)控制
                h ^= h << 1; h ^= h >>> 3; h ^= h << 10;
                if (h == 0)
                    h = SPINS | (int)t.getId();
                else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0)
                    Thread.yield();
            }
            // p是ThreadLocal記錄的當(dāng)前線程的Node。
            // 如果slot不是p表示slot是別的線程放進(jìn)去的
            else if (slot != p)
                spins = SPINS;
            else if (!t.isInterrupted() && arena == null &&
                     (!timed || (ns = end - System.nanoTime()) > 0L)) {
                U.putObject(t, BLOCKER, this);
                p.parked = t;
                if (slot == p)
                    U.park(false, ns);
                p.parked = null;
                U.putObject(t, BLOCKER, null);
            }
            else if (U.compareAndSwapObject(this, SLOT, p, null)) {
                // 沒(méi)有被中斷但是超時(shí)了,返回TIMED_OUT,否則返回null
                v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
                break;
            }
        }
        // match設(shè)置為null值 CAS
        U.putOrderedObject(p, MATCH, null);
        p.item = null;
        p.hash = h;
        // 返回獲取的對(duì)方線程交換來(lái)的數(shù)據(jù)
        return v;
    }

arenaExchange的實(shí)現(xiàn):

   /**
     * Exchange function when arenas enabled. See above for explanation.
     * 當(dāng)啟用arenas的時(shí)候,使用該方法進(jìn)行線程間的數(shù)據(jù)交換。
     * @param item 本線程要交換的非null數(shù)據(jù)。
     * @param timed 如果需要計(jì)時(shí)等待,則設(shè)置為true。
     * @param ns 表示計(jì)時(shí)等待的最大時(shí)長(zhǎng)。
     * @return 對(duì)方線程交換來(lái)的數(shù)據(jù)。如果線程被中斷,或者等待超時(shí),則返回null。
     */
    private final Object arenaExchange(Object item, boolean timed, long ns) {
        Node[] a = arena;
        Node p = participant.get();
        // 訪問(wèn)下標(biāo)為i處的slot數(shù)據(jù)
        for (int i = p.index;;) {                      // access slot at i
            int b, m, c; long j;                       // j is raw array offset
            Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
            // 如果q不是null,則將數(shù)組的第j個(gè)元素由q設(shè)置為null
            if (q != null && U.compareAndSwapObject(a, j, q, null)) {
                // 獲取對(duì)方線程交換來(lái)的數(shù)據(jù)
                Object v = q.item;                     // release
                // 設(shè)置本方線程交換的數(shù)據(jù)
                q.match = item;
                Thread w = q.parked;
                if (w != null)
                    // 如果對(duì)方線程非空,則喚醒對(duì)方線程
                    U.unpark(w);
                return v;
            }
            // 如果自旋次數(shù)沒(méi)達(dá)到邊界,且q為null
            else if (i <= (m = (b = bound) & MMASK) && q == null) {
                // 提供本方數(shù)據(jù)
                p.item = item;                         // offer
                // 將arena的第j個(gè)元素由null設(shè)置為p
                if (U.compareAndSwapObject(a, j, null, p)) {
                    long end = (timed && m == 0) ? System.nanoTime() + ns : 0L;
                    Thread t = Thread.currentThread(); // wait
                    // 自旋等待
                    for (int h = p.hash, spins = SPINS;;) {
                        // 獲取對(duì)方交換來(lái)的數(shù)據(jù)
                        Object v = p.match;
                        // 如果對(duì)方交換來(lái)的數(shù)據(jù)非空
                        if (v != null) {
                            // 將p設(shè)置為null,CAS操作
                            U.putOrderedObject(p, MATCH, null);
                            // 清空
                            p.item = null;             // clear for next use
                            p.hash = h;
                            // 返回交換來(lái)的數(shù)據(jù)
                            return v;
                        }
                        // 產(chǎn)生隨機(jī)數(shù),用于限制自旋次數(shù)
                        else if (spins > 0) {
                            h ^= h << 1; h ^= h >>> 3; h ^= h << 10; // xorshift
                            if (h == 0)                // initialize hash
                                h = SPINS | (int)t.getId();
                            else if (h < 0 &&          // approx 50% true
                                     (--spins & ((SPINS >>> 1) - 1)) == 0)
                                Thread.yield();        // two yields per wait
                        }
                        // 如果arena的第j個(gè)元素不是p
                        else if (U.getObjectVolatile(a, j) != p)
                            spins = SPINS;       // releaser hasn't set match yet
                        else if (!t.isInterrupted() && m == 0 &&
                                 (!timed ||
                                  (ns = end - System.nanoTime()) > 0L)) {
                            U.putObject(t, BLOCKER, this); // emulate LockSupport
                            p.parked = t;              // minimize window
                            if (U.getObjectVolatile(a, j) == p)
                                U.park(false, ns);
                            p.parked = null;
                            U.putObject(t, BLOCKER, null);
                        }
                        // arena的第j個(gè)元素是p并且CAS設(shè)置arena的第j個(gè)元素由p設(shè)置 為null成功
                        else if (U.getObjectVolatile(a, j) == p &&
                                 U.compareAndSwapObject(a, j, p, null)) {
                            if (m != 0)                // try to shrink
                                U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1);
                            p.item = null;
                            p.hash = h;
                            i = p.index >>>= 1;        // descend
                            // 如果線程被中斷,則返回null值
                            if (Thread.interrupted())
                                return null;
                            if (timed && m == 0 && ns <= 0L)
                                // 如果超時(shí),返回TIMED_OUT。
                                return TIMED_OUT;
                            break;                     // expired; restart
                        }
                    }
                }
                else
                    p.item = null;                     // clear offer
            }
            else {
                if (p.bound != b) {                    // stale; reset
                    p.bound = b;
                    p.collides = 0;
                    i = (i != m || m == 0) ? m : m - 1;
                }
                else if ((c = p.collides) < m || m == FULL ||
                         !U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) {
                    p.collides = c + 1;
                    i = (i == 0) ? m : i - 1;          // cyclically traverse
                }
                else
                    i = m + 1;                         // grow
                p.index = i;
            }
        }
    }

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容