JDK-CountDownLatch-實例、源碼和模擬實現

Conception

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
        根據CountDownLatch的document,就是說CountDownLatch作爲一個同步的助手,可以阻塞一個線程,等待另一個線程結束了再執行。
        所以CountDownLatch的作用在於:通過阻塞來協調線程間的執行順序。
        CountDownLatch最重要的方法爲await()和countDown()。首先,初始化指定一個count,每次countDown()都會count--,而await()就是阻塞調用它的方法,直到count==0。
        所以可以把CountDownLatch看成同步的計數器,什麼時候倒數到0就執行,否則阻塞。

Demo

        我們首先做一個CountDownLatch的demo,看看它是怎麼一個阻塞。
        count初始化爲2,所以count.await()會阻塞主線程,直到兩個processor都countDown(),即count==0的時候纔會執行,打印出"Processors has been done."
public class CountDownLatchTest {
	public static void main(String[] args) throws InterruptedException {
		CountDownLatch count = new CountDownLatch(2);
		ExecutorService exc = Executors.newSingleThreadExecutor();
		
		System.out.println("Before submit the processor.");
		exc.submit(new CountProcessor(count, "P1"));
		exc.submit(new CountProcessor(count, "P2"));
		System.out.println("Has submited the processor.");

		count.await();
		System.out.println("Processors has been done.");
		exc.shutdown();
	}
}

class CountProcessor extends Thread {
	private CountDownLatch count;
	private String name;
	public CountProcessor(CountDownLatch count, String name) {
		this.count = count;
		this.name = name;
	}
	@Override
	public void run() {
		try {
			Thread.currentThread().sleep(1000);
			System.out.println(this.name + " has been done.");
			count.countDown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


        修改一下上面的程序,我們可以模擬一個百米賽跑的現場:
        首先運動員進場:前期數據準備和線程池的初始化。
        準備工作完成後統一起跑:傳入的begin.await()一直阻塞着Runner.run(),在前期準備完成後,begin.countDown()後begin.count==0,阻塞結束,Runner起跑。
        比賽一直進行,直到所有選手衝過終點:end.await()會阻塞主線程,所以最後的那條print語句不會提前打印,而是等待Runner.run()結束執行end.countDown(),減少3次直到end.count==0才接觸阻塞,宣佈比賽結束。
        這就是CountDownLatch的經典場景,統一開始,統一結束。
public class CountDownLatchTest {
	public static void main(String[] args) throws InterruptedException {
		CountDownLatch begin = new CountDownLatch(1);
		CountDownLatch end = new CountDownLatch(3);
		ExecutorService exc = Executors.newCachedThreadPool();
		
		System.out.println("Runners are comming.");
		exc.submit(new CountProcessor(begin, end, "Runner_1", 1000));
		exc.submit(new CountProcessor(begin, end, "Runner_2", 2000));
exc.submit(new CountProcessor(begin, end, "Runner_3", 3000));
		System.out.println("Ready.");
		
		Thread.currentThread().sleep(1000);
		System.out.println("Go.");
		begin.countDown();
		
		end.await();
		System.out.println("All runners Finish the match.");
		exc.shutdown();
	}
}

class CountProcessor extends Thread {
	private CountDownLatch beginCount;
	private CountDownLatch endCount;
	private String name;
	private int runningTime;
	public CountProcessor(CountDownLatch beginCount, CountDownLatch endCount, String name, int runningTime) {
		this.beginCount = beginCount;
		this.endCount = endCount;
		this.name = name;
		this.runningTime = runningTime;
	}

	@Override
	public void run() {
		try {
			this.beginCount.await();
			System.out.println(this.name + " start.");
			Thread.currentThread().sleep(this.runningTime);
			System.out.println(this.name + " breast the tape.");
			this.endCount.countDown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}


Source Code

        我們來看JDK中CountDownLatch是怎麼定義的,以及其中的主要方法。
public class CountDownLatch {
public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
}

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

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

public long getCount() {
        return sync.getCount();
}
}


        很顯然,CountDownLatch的邏輯都封裝在內部類Sync中,這也是通過裝飾者模式來提高封裝性的普遍做法。
        Sync繼承了AbstractQueuedSynchronizer,這裏的調用關係有點麻煩,我就不一一展開了,我貼了部分關鍵的代碼(如下)。
        概括一下,CountDownLatch的count存放在以volatile聲明的變量state。在await()的時候for輪詢state,一直輪詢以達到阻塞的效果,直到state==0。而countDown()就是通過CompareAndSet的方式來遞減state。
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;
            }
        }
}

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer {
private volatile int state;
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);
        }
}
}


Simulator

        從上面可以看出來,其實CountDownLatch的實現很簡單,countDown的時候原子修改,await的時候循環判斷volatile來阻塞。
        所以完全可以通過原子類來實現,而且還更加beautiful。以下,我用AtomicInteger來模擬CountDownLatch的實現。
        測試程序還是和上面的賽跑程序一樣的,只是把CountDownLatch替換成了CountDownLatchSimulator。
        最後測試結果還是一樣的,只是過程簡單了許多,因爲JDK把很多防止衝突的邏輯都封裝在原子類了。其中await的時候,可以在循環中加上sleep,能減低系統輪詢的消耗。
我只是做了一個簡單的實現,至於性能和安全性方面,還希望大神們指導大笑
public class CountDownLatchSimulator {
	private AtomicInteger count;
	public CountDownLatchSimulator(int count) {
		this.count = new AtomicInteger(count);
	}
	
	public void await() throws InterruptedException {
		while (this.count.get() != 0) {
//			Thread.currentThread().sleep(100);
		}
	}
	
	public void countDown() {
		this.count.getAndDecrement();
	}
	
	public static void main(String[] args) throws InterruptedException {
		CountDownLatchSimulator begin = new CountDownLatchSimulator(1);
		CountDownLatchSimulator end = new CountDownLatchSimulator(3);
		ExecutorService exc = Executors.newCachedThreadPool();
		
		System.out.println("Runners are comming.");
		exc.submit(new CountProcessor2(begin, end, "Runner_1", 1000));
		exc.submit(new CountProcessor2(begin, end, "Runner_2", 2000));
		exc.submit(new CountProcessor2(begin, end, "Runner_3", 3000));
		System.out.println("Ready.");
		
		Thread.currentThread().sleep(2000);
		System.out.println("Go.");
		begin.countDown();
		
		end.await();
		System.out.println("All runners Finish the match.");
		exc.shutdown();
	}
}

class CountProcessor2 extends Thread {
	
	private CountDownLatchSimulator beginCount;
	private CountDownLatchSimulator endCount;
	private String name;
	private int runningTime;
	public CountProcessor2(CountDownLatchSimulator beginCount, CountDownLatchSimulator endCount, String name, int runningTime) {
		this.beginCount = beginCount;
		this.endCount = endCount;
		this.name = name;
		this.runningTime = runningTime;
	}
	
	@Override
	public void run() {
		try {
			this.beginCount.await();
			System.out.println(this.name + " start.");
			Thread.currentThread().sleep(this.runningTime);
			System.out.println(this.name + " breast the tape.");
			this.endCount.countDown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
發佈了44 篇原創文章 · 獲贊 11 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章