目录
一.简述
在 JUC学习(一):Semaphore信号量 中可以看到,信号量可以做到控制不同批次的线程同步运行,但是对于同一批线程,它们之间还是乱序执行的。在某些情况下,需要控制线程同时开始运行,这时候就需要使用CountDownLatch。
从名字来看,这是一个计数值只降不升的“闩”,可以猜到,它的使用方式就是设置计数器值,每有一个线程准备就绪,就将计数器值下降,当计数器清零时,门闩打开,线程们就可以同时开始执行。下面是示例代码:
public class CountDownLatchTest {
private static CountDownLatch down = new CountDownLatch(1);
public static void testMethod(){
try{
System.out.println("等待"+Thread.currentThread().getName()+"准备就绪");
down.await();
System.out.println(Thread.currentThread().getName()+"开始运行!");
}catch (Exception e){
}
}
public static void downMethod(){
System.out.println("线程"+Thread.currentThread().getName()+"要叫醒子线程了!");
down.countDown();
}
static class MyThread extends Thread{
@Override
public void run() {
testMethod();
}
}
public static void main(String args[]) throws InterruptedException {
new MyThread().start();
try {
Thread.sleep(2000);
downMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果如下:
等待Thread-0准备就绪
线程main要叫醒子线程了!
Thread-0开始运行!
可以看到,Thread-0在调用down.await()后,就进入等待状态,不再继续运行。主线程sleep两秒钟后,通过调用down.countDown()将计数器清零,这时才唤醒Thread-0,使其继续工作。
假如计数器清零之后再运行countDown,则不会有任何效果。
由此可见,await和countDown是CountDownLatch的核心方法。
二.构造方法
在构造CountDownLatch实例时,传入的也是一个int值,联系Semaphore,不难猜测它们原理大致相同。看源码果然如此:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync类和Semaphore一样,也是一个AQS抽象子类,Sync的构造方法也是设置AQS的state变量值。
不同的是,信号量在构造时并没有限制permits必须大于等于0,而CountDownLatch由于count值只降不升,因此必须保证初始值不小于0。
三.await方法
await方法的效果是阻塞当前线程,直到计数器清零。很容易联想到信号量的acquire方法,相反的是,acquire是阻塞线程,直到计数器值大于等于申请许可数。因此,它们都调用了AQS的同名方法;await的重载版本接受时间作为参数,表示等待若干时间后结束等待,和tryAcquire类似,相应的,它们也调用了同名方法:
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
tryAcquireSharedNanos源码以及acquireSharedInterruptibly调用的doAcquireSharedInterruptibly方法源码与 JUC学习(一):Semaphore信号量 相同,不再赘述。
下面看下acquireSharedInterruptibly调用的tryAcquireShared:
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
代码只有一行,就是判断当前计数器值是否为0,是则继续执行线程任务,否则阻塞线程直到计数器清零。
四.countDown方法
既然await对应了acquire,那么countDown自然对应了release方法。与await类似,CountDownLatch也仅重写了try方法:
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;
}
}
首先判断当前计数值是否已经为0,是就没必要继续运行,直接退出。然后CAS地将计数值减1,并返回新计数值是否为0。假如计数值为0,那么就通过doReleaseShared方法,将阻塞队列中的线程唤醒。