【Java並發編程實戰】—–“J.U.C”:CountDownlatch

來源:互聯網
上載者:User

【Java並發編程實戰】—–“J.U.C”:CountDownlatch

上篇博文(【Java並發編程實戰】—–“J.U.C”:CyclicBarrier)LZ介紹了CyclicBarrier。CyclicBarrier所描述的是“允許一組線程互相等待,直到到達某個公用屏障點,才會進行後續任務”。而CountDownlatch和它也有一點點相似之處:CountDownlatch所描述的是“在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待”。在JDK API中是這樣闡述的:

用給定的計數 初始化 CountDownLatch。由於調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之後,會釋放所有等待的線程,await 的所有後續調用都將立即返回。這種現象只出現一次——計數無法被重設。如果需要重設計數,請考慮使用 CyclicBarrier。

CountDownLatch 是一個通用同步工具,它有很多用途。將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,或入口:在通過調用 countDown() 的線程開啟入口前,所有調用 await 的線程都一直在入口處等待。用 N 初始化的 CountDownLatch 可以使一個線程在 N 個線程完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。

CountDownLatch 的一個有用特性是,它不要求調用 countDown 方法的線程等到計數到達零時才繼續,而在所有線程都能通過之前,它只是阻止任何線程繼續通過一個 await。

雖然,CountDownlatch與CyclicBarrier有那麼點相似,但是他們還是存在一些區別的:

1、CountDownLatch的作用是允許1或N個線程等待其他線程完成執行;而CyclicBarrier則是允許N個線程相互等待。

2、 CountDownLatch的計數器無法被重設;CyclicBarrier的計數器可以被重設後使用,因此它被稱為是迴圈的barrier。

CountDownLatch分析

CountDownLatch結構如下:

從中可以看出CountDownLatch依賴Sync,其實CountDownLatch內部採用的是共用鎖定來實現的(內部Sync的實現可以看出)。它的建構函式如下:

CountDownLatch(int count):構造一個用給定計數初始化的 CountDownLatch。

public CountDownLatch(int count) {        if (count < 0) throw new IllegalArgumentException(count < 0);        this.sync = new Sync(count);    }

 

以下原始碼可以證明,CountDownLatch內部是採用共用鎖定來實現的:

private static final class Sync extends AbstractQueuedSynchronizer {        private static final long serialVersionUID = 4982264981922014374L;        protected int tryAcquireShared(int acquires) {            /** 省略原始碼 **/        }        protected boolean tryReleaseShared(int releases) {           /** 省略原始碼 **/        }    }

 

CountDownLatch提供了await方法來實現:

await():使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷。

await(long timeout, TimeUnit unit): 使當前線程在鎖存器倒計數至零之前一直等待,除非線程被中斷或超出了指定的等待時間。

public void await() throws InterruptedException {        sync.acquireSharedInterruptibly(1);    }

 

await內部調用sync的acquireSharedInterruptibly方法:

public final void acquireSharedInterruptibly(int arg)            throws InterruptedException {        //線程中斷,拋出InterruptedException異常        if (Thread.interrupted())            throw new InterruptedException();        if (tryAcquireShared(arg) < 0)            doAcquireSharedInterruptibly(arg);    }

 

acquireSharedInterruptibly()的作用是擷取共用鎖定。如果在擷取共用鎖定過程中線程中斷則拋出InterruptedException異常。否則通過tryAcquireShared方法來嘗試擷取共用鎖定。如果成功直接返回,否則調用doAcquireSharedInterruptibly方法。

tryAcquireShared源碼:

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

 

tryAcquireShared方法被CountDownLatch重寫,他的主要作用是嘗試著擷取鎖。getState == 0 表示鎖處於可擷取狀態返回1否則返回-1;當tryAcquireShared返回-1擷取鎖失敗,調用doAcquireSharedInterruptibly擷取鎖:

private void doAcquireSharedInterruptibly(int arg)            throws InterruptedException {            //建立當前線程(共用鎖定)Node節點            final Node node = addWaiter(Node.SHARED);            boolean failed = true;            try {                for (;;) {                    //擷取當前節點的前繼節點                    final Node p = node.predecessor();                    //如果當前節點為CLH列頭,則嘗試擷取鎖                    if (p == head) {                        //擷取鎖                        int r = tryAcquireShared(arg);                        if (r >= 0) {                            setHeadAndPropagate(node, r);                            p.next = null; // help GC                            failed = false;                            return;                        }                    }                    //如果當前節點不是CLH列頭,當前線程一直等待,直到擷取鎖為止                    if (shouldParkAfterFailedAcquire(p, node) &&                        parkAndCheckInterrupt())                        throw new InterruptedException();                }            } finally {                if (failed)                    cancelAcquire(node);            }        }

 

該方法當中的方法,前面部落格都講述過,請參考:【Java並發編程實戰】—–“J.U.C”:ReentrantLock之二lock方法分析、【Java並發編程實戰】—–“J.U.C”:Semaphore。

CountDownLatch,除了提供await方法外,還提供了countDown(),countDown所描述的是“遞減鎖存器的計數,如果計數到達零,則釋放所有等待的線程。”,源碼如下:

public void countDown() {        sync.releaseShared(1);    }

 

countDown內部調用releaseShared方法來釋放線程:

public final boolean releaseShared(int arg) {        //嘗試釋放線程,如果釋放釋放則調用doReleaseShared()        if (tryReleaseShared(arg)) {            doReleaseShared();            return true;        }        return false;    }

 

tryReleaseShared,同時被CountDownLatch重寫了:

protected boolean tryReleaseShared(int releases) {        for (;;) {            //擷取鎖狀態            int c = getState();            //c == 0 直接返回,釋放鎖成功            if (c == 0)                return false;            //計算新“鎖計數器”            int nextc = c-1;            //更新鎖定狀態(計數器)            if (compareAndSetState(c, nextc))                return nextc == 0;        }    }

 

總結:

CountDownLatch內部通過“共用鎖定”實現。在建立CountDownLatch時,需要傳遞一個int類型的count參數,該count參數為“鎖狀態”的初始值,該值表示著該“共用鎖定”可以同時被多少線程擷取。當某個線程調用await方法時,首先判斷鎖的狀態是否處於可擷取狀態(其條件就是count==0?),如果共用鎖定可擷取則擷取共用鎖定,否則一直處於等待直到擷取為止。當線程調用countDown方法時,計數器count – 1。當在建立CountDownLatch時初始化的count參數,必須要有count線程調用countDown方法才會使計數器count等於0,鎖才會釋放,前面等待的線程才會繼續運行。

執行個體

員工開會只有當所有人到期之後才會開戶。我們初始化與會人員為3個,那麼CountDownLatch的count應為3:

public class Conference implements Runnable{    private final CountDownLatch countDown;        public Conference(int count){        countDown = new CountDownLatch(count);    }        /**     * 與會人員到達,調用arrive方法,到達一個CountDownLatch調用countDown方法,鎖計數器-1     * @author:chenssy     * @data:2015年9月6日     *     * @param name     */    public void arrive(String name){        System.out.println(name + 到達.....);        //調用countDown()鎖計數器 - 1        countDown.countDown();        System.out.println(還有  + countDown.getCount() + 沒有到達...);    }        @Override    public void run() {        System.out.println(準備開會,參加會議人員總數為: + countDown.getCount());        //調用await()等待所有的與會人員到達        try {            countDown.await();        } catch (InterruptedException e) {        }        System.out.println(所有人員已經到達,會議開始.....);    }}

 

參加與會人員Participater:

public class Participater implements Runnable{    private String name;    private Conference conference;        public Participater(String name,Conference conference){        this.name = name;        this.conference = conference;    }    @Override    public void run() {        conference.arrive(name);    }}

 

Test:

public class Test {    public static void main(String[] args) {        //啟動會議室線程,等待與會人員參加會議        Conference conference = new Conference(3);        new Thread(conference).start();                for(int i = 0 ; i < 3 ; i++){            Participater participater = new Participater(chenssy-0 + i , conference);            Thread thread = new Thread(participater);            thread.start();        }    }}

 

運行結果:

準備開會,參加會議人員總數為:3chenssy-01到達.....還有 2沒有到達...chenssy-00到達.....還有 1沒有到達...chenssy-02到達.....還有 0沒有到達...所有人員已經到達,會議開始.....

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.