另一个并发工具,CountDownLatch,和CyclicBarrier功能上有点类似,但是在实现上和是不同的。
简单的说,也是一个计数器,和CyclicBarrier相比,一定方面是更加灵活的,CountDownLatch可以在任意代码处通知自己已完成,等所有都做完时,主线程就可以接下来运行了。有需要可参看:Java并发学习(十六)-并发工具CyclicBarrier 。
CountDownLatch里面维护这一个AbstractQueuedSynchronizer的子类,其实就是基于AQS的共享锁的应用。
看看Sync的代码:
private static final class Sync extends AbstractQueuedSynchronizer {
//count去初始化state。
Sync(int count) {
setState(count);
}
//获取state。
int getCount() {
return getState();
}
//重写父类方法,获取共享锁。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/**
* 释放锁。
* 自减state。
*/
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;
}
}
}
上述代码只是简单的对AQS的state变量的CAS原子操作。
下面通过主要方法await
和countDown
来分析里面的实现逻辑。
一般我们用关于锁的方法时,都是lock和unlock,但是这里是await,你可以理解为,当所有线程执行完后,再需要执行的线程a,那么这个a就可以先await,等到其他的执行完,a就可以执行了。
首先执行await
:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
再看父类的acquireSharedInterruptibly
方法:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//<0,代表没有获取到锁,所以需要自旋方式再次尝试,
//结果可能被park挂起,或者获取到锁
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
我们再看看子类重写的tryAcquireShared
方法:
//重写父类方法,获取共享锁。
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
当state为0时候,结果为1,放到acquireSharedInterruptibly
里面就是获取到锁;
而当state不为0,则结果为-1,也就是代表没有获取到锁。
而最开始CountDownLatch的构造方法:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
所以count会传入大于0的,代表等待着count个线程。
现在就明了了,调用这个方法是用来阻塞的。
countDown什么时候用呢?
上面说过,其他的众多运行线程,countDown方法就是其他众多运行线程用,当你的工作线程运行完后,旧值性countDown方法。
public void countDown() {
sync.releaseShared(1);
}
再看父类的releaseShared方法:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared方法在上面Sync里面,主要就是对count值自减,而doReleaseShared则是unpark唤醒后继符合条件线程。
从Java doc里面可以看到两个典型例子,这里以例说明。
class Driver2 {
void main() throws InterruptedException {
CountDownLatch doneSignal = new CountDownLatch(N);
//线程池执行
Executor e = ...
for (int i = 0; i < N; ++i) // create and start threads
e.execute(new WorkerRunnable(doneSignal, i));
//等待,主线程main的await后面代码挂起等待
doneSignal.await();
//这后面方法,必须等所有其他的都countDown一次,才会被唤醒执行。
afterawait();
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
try {
doWork(i);
//我做完了,我利用countDown通知下,但是不阻塞当前线程
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
}
//具体工作方法
void doWork() { ... }
}
任务分割情况,就是把让其他众多线程a1,a2,a3…..全部运行完后,主线程b才能够执行。注意需要共用这一个CountDownLatch。
下面是一对类,其中一组工作线程使用两个倒计时锁存器:
class Driver { // ...
void main() throws InterruptedException {
//定义两个CountDownLatch
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();
//做完后,调用startSiganl的countDown
startSignal.countDown();
//做另外一件事
doSomethingElse();
//等待doneSignal的信号
doneSignal.await();
}
}
class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
//首先等待startSignal的信号
startSignal.await();
//等到信号了,做事
doWork();
//对doneSignal发出信号,说做好了
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
}
void doWork() { ... }
}
从功能和实现上,CountDownLatch还是有点区别的:
在Thread里面,还有个join方法,用于即阻塞当前线程直到执行join的线程运行完:
public class JoinCountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
public void run() {
System.out.println("parser1 finish");
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
System.out.println("parser2 finish");
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("all parser finish");
}
}
所以对与join也有等待线程的功能,但是来看join的代码实现:
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
//判断等待时间
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//默认等待时间
if (millis == 0) {
//如果当前线程仍然存活,那么就一直等待
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
最后,直到join线程中止后,线程的this.notifyAll会被调用。所以main线程最后会被唤醒,当然,join方法里面使用了传统的synchronized关键字,以及wait,notify,notifyAll等方法,JDK不推荐在线程实例上使用wait,notify和notifyAll方法。
参考资料:
1. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html
2. http://ifeve.com/talk-concurrency-countdownlatch/