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