概述
CountDownLatch可以翻譯為倒計(jì)數(shù)器,讓主調(diào)用線程等待其他一些線程工作完成后纳账,再繼續(xù)運(yùn)行绩蜻。(相當(dāng)于調(diào)用所有子線程join方法的效果)
主要有兩種使用場景:
第一種設(shè)置兩個(gè)信號厚掷,一個(gè)是啟動(dòng)信號礁苗,當(dāng)控制線程發(fā)出信號以后,所有線程才開始工作徙缴,一個(gè)是全部完成的信號试伙,當(dāng)所有工作線程完成后,控制線程才繼續(xù)工作于样。
另一個(gè)典型用法是將問題分為N個(gè)部分疏叨,每一部分用子線程跑,然后在鎖存器上遞減計(jì)數(shù)穿剖,當(dāng)所有子線程都完成后蚤蔓,調(diào)用線程將能夠繼續(xù)運(yùn)行。
例子
public static void main(String[] args) throws InterruptedException {
final int N = 3;
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i)
new Thread(new Worker(startSignal, doneSignal)).start();
doSomethingElse();
startSignal.countDown();
doSomethingElse();
doneSignal.await();
System.out.println(currentThread().getName() + ": end");
}
private static void doSomethingElse() {
System.out.println(currentThread().getName() + ": do something");
}
static class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
public Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
@Override
public void run() {
try {
startSignal.await();
doWork();
System.out.println(currentThread().getName() + ": finish work");
doneSignal.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void doWork() {
System.out.println(currentThread().getName() + ": is working");
}
}
運(yùn)行結(jié)果:
main: do something
main: do something
Thread-1: is working
Thread-0: is working
Thread-2: is working
Thread-2: finish work
Thread-0: finish work
Thread-1: finish work
main: end
例子github地址
主線程設(shè)置了兩個(gè)信號量糊余,startSignal秀又,doneSignal
startSignal設(shè)置的state為1,doneSignal設(shè)置的state為N需要等待的線程量
當(dāng)主線程在main函數(shù)中調(diào)用startSignal的countDown方法后贬芥,所有的子線程就可以開始做自己的事了吐辙,當(dāng)主線程調(diào)用doneSignal.await,開始等待蘸劈,知道所有子線程完成doneSignal.countDown后昏苏,主線程繼續(xù)執(zhí)行。
可以想像成一個(gè)體育老師千米測試威沫,所有學(xué)生準(zhǔn)備就緒贤惯,等老師發(fā)出開始信號startSignal.countDown,學(xué)生們都甩開膀子跑了棒掠,老師走的千米測試的終點(diǎn)后孵构,調(diào)用doneSignal.await,這時(shí)老師就要等所有學(xué)生全部跑完后烟很,才能下課浦译,開始自己的生活了。
CountDownLatch源碼
CountDownLatch使用的是AQS的共享式占用鎖溯职,定義了一個(gè)內(nèi)部類Sync繼承AQS精盅,不清楚AQS源碼的需要先看下這篇文章
//只有一個(gè)入?yún)⑹莍nt的構(gòu)造函數(shù),用來初始化Sync同步器
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
//重寫了父類嘗試請求加鎖的方法谜酒,判斷只有state為0叹俏,返回正數(shù),即請求加鎖成功
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// 無限循環(huán)CAS操作state的值
for (;;) {
int c = getState();
//說明已經(jīng)釋放過了僻族,不需要再喚醒了
if (c == 0)
return false;
int nextc = c-1;
//當(dāng)state值減為0以后粘驰,共享鎖釋放屡谐,喚起等待節(jié)點(diǎn)
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//調(diào)用上面的嘗試加鎖方法
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
await方法就是判斷state的值,如果state值不為0蝌数,加鎖失敗愕掏,進(jìn)入排隊(duì)等待狀態(tài)。等待其他線程釋放鎖顶伞。
public void countDown() {
sync.releaseShared(1);
}
countDown方法比較簡單就是對state減1饵撑,當(dāng)state等于0,喚起等待的線程唆貌。