Java併發之CountDownLatch

    在併發操作中,當需要當前線程c等待另一線程a結束後在運行的話,我們首先想到的是join方法,在c線程運行中調用a.join(),該方法會使當前線程阻塞於a,直到線程a運行結束,JVM調用a.notifyAll()方法喚醒z。

    在Java1.5之後,併發包提供的CountDownLatch也可以實現join功能,並且更爲強大。

一、使用方法

    下面放出一個簡單的Demo:

public static void main(String[]args){
		CountDownLatch c = new CountDownLatch(2);
		Thread a = new Thread(new Runnable(){

			@Override
			public void run() {
				try {
					Thread.currentThread().sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("a");
				c.countDown();
			}
				
		});
		Thread b = new Thread(new Runnable(){

			@Override
			public void run() {
				try {
					Thread.currentThread().sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("b");
				c.countDown();
			}
				
		});
		a.start();
		b.start();
		try {
			c.await();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("main");
	}
/*
a
b
main
*/

    CountDownLatch的構造函數接收一個int類型的參數作爲計數器,如果你想等待N個點完成,這裏就傳入N。每當調用CountDownLatch的countDown方法,N就會減1。CountDownLatch的await方法會阻塞當前線程,知道N變爲0。由於countDown方法可以用在任何地方,所以既可以是N個線程, 也可以是1個線程N個步驟。在使用多線程時,只需要把這個CountDownLatch的引用傳入線程即可。此外,CountDownLatch也允許多個線程調用await方法,同時阻塞多個線程。

    還有一點要注意的是,CountDownLatch的計數器無法被重置。

二、實現原理

    同讀寫鎖一樣,CountDownLatch本質上也是一個共享鎖。它允許一個或多個線程等待其他線程。它的實驗原理也是同讀寫鎖一樣,通過隊列同步器(AbstractQueuedSynchronizer AQS)實現的:

    構造方法:

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

    構造器傳入的計數count被用來構造一個Sync(AQS)對象。Sync的構造函數如下:

       Sync(int count) {
            setState(count);	//設置同步狀態
        }

    這裏,同步器的state就是一個鎖狀態,當state大於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方法,如下:

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

    嘗試獲取共享鎖,當鎖計數器==0,即鎖是可以獲取的,則返回1,獲取鎖成功,直接退出acquireSharedInterruptibly方法。否則返回-1,進入doAcquireSharedInterruptibly方法阻塞當前線程。doAcquireSharedInterruptibly方法源碼如下:

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.SHARED);	//創建當前線程的Node節點,且標記爲共享鎖,將鎖加入CLH隊列末尾
        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);
        }
    }
  

    countDown方法

    public void countDown() {
        sync.releaseShared(1);
    }
    實際上調用隊列同步器的釋放鎖方法:
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;


    首先會嘗試直接釋放共享鎖,成功則直接返回,否則調用doReleaseShared();

        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;			//狀態-1
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }


三、總結

    CountDownLatch是通過共享鎖實現的。在創建CountDownLatch中時,會傳遞一個int類型參數count,該參數是隊列同步器中的state狀態值,表示共享鎖最多能被count線程同時獲取。當某線程調用該CountDownLatch的await()方法時,該線程會等待共享鎖可用時,才能獲取共享鎖。而共享鎖的可用條件就是state等於0。只有每次調用CountDownLatch的countDown方法,狀態值纔會減1。


 


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