概述
CountDownLatch是一个用来控制并发的很常见的工具,它允许一个或者多个线程等待其他的线程执行到某一操作,比如说需要去解析一个excel的数据,为了更快的解析则每个sheet都使用一个线程去进行解析,但是最后的汇总数据的工作则需要等待每个sheet的解析工作完成之后才能进行,这就可以使用CountDownLatch。
CountDownLatch是java.util.concurrent包中一个类,CountDownLatch只要提供的机制是多个(具体数量等于初始化CountDownLatch时count的值)线程都达到了预期状态或者完成了预期工作时触发事件,其他线程可以等待这个事件来触发自己后续的工作。等待的线程可以是多个,即CountDownLatch可以唤醒多个等待的线程。到达自己预期状态的线程会调用CountDownLatch的countDown方法,而等待的线程会调用CountDownLatch的await方法。
这里写图片描述
结合以下几个例子,可以快速掌握这个这个类的基本使用方法:
例一:
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDown = new CountDownLatch(1);
CountDownLatch await = new CountDownLatch(5);
// 依次创建并启动处于等待状态的5个MyRunnable线程
for (int i = 0; i < 5; ++i) {
new Thread(new MyRunnable(countDown, await)).start();
}
System.out.println("用于触发处于等待状态的线程开始工作......");
System.out.println("用于触发处于等待状态的线程工作完成,等待状态线程开始工作......");
countDown.countDown();
await.await();
System.out.println("Bingo!");
}
public class MyRunnable implements Runnable {
private final CountDownLatch countDown;
private final CountDownLatch await;
public MyRunnable(CountDownLatch countDown, CountDownLatch await) {
this.countDown = countDown;
this.await = await;
}
public void run() {
try {
countDown.await();//等待主线程执行完毕,获得开始执行信号...
System.out.println("处于等待的线程开始自己预期工作......");
await.countDown();//完成预期工作,发出完成信号...
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
用于触发处于等待状态的线程开始工作......
用于触发处于等待状态的线程工作完成,等待状态线程开始工作......
处于等待的线程开始自己预期工作......
处于等待的线程开始自己预期工作......
处于等待的线程开始自己预期工作......
处于等待的线程开始自己预期工作......
处于等待的线程开始自己预期工作......
Bingo!
例子二:
public class TestDemo2 {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));
int count = 10;
final CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; i++) {
threadPool.execute(new MyRunnable1(latch, i));
}
latch.await();
System.err.println("等待线程被唤醒!");
threadPool.shutdown();
}
}
class MyRunnable1 implements Runnable {
CountDownLatch latch = null;
int i;
public MyRunnable1(CountDownLatch latch, int i) {
this.latch = latch;
this.i = i;
}
@Override
public void run() {
System.err.println("线程" + i +"完成了操作...");
try {
Thread.currentThread();
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
}
}
运行结果:
线程0完成了操作...
线程3完成了操作...
线程2完成了操作...
线程1完成了操作...
线程4完成了操作...//暂停4秒
线程5完成了操作...
线程6完成了操作...
线程8完成了操作...
线程7完成了操作...
线程9完成了操作...//暂停4秒
等待线程被唤醒
例子详解:
这里有三个线程(main,thread1,thread2),其中main线程将调用countDownLatch的await方法去等待另外两个线程的某个操作的结束(调用countDownLatch的countDown方法)。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException{
CountDownLatch countDownLatch = new CountDownLatch(2){
@Override
public void await() throws InterruptedException {
super.await();
System.out.println(Thread.currentThread().getName() + " count down is ok");
}
};
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//do something
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is done");
countDownLatch.countDown();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//do something
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is done");
countDownLatch.countDown();
}
}, "thread2");
thread1.start();
thread2.start();
countDownLatch.await();
}
}
输出:
test thread1 is done
test thread2 is done
test thread3 is done
test thread3 count down is ok
main count down is ok
这里的CountDownLatch的构造函数中使用的int型变量的意思是需要等待多少个操作 的完成。这里是2所以需要等到调用了两次countDown()方法之后主线程的await()方法才会返回。这意味着如果我们错误的估计了需要等待的操作的个数或者在某个应该调用countDown()方法的地方忘记了调用那么将意味着await()方法将永远的阻塞下去。
实现原理
CountDownLatch类实际上是使用计数器的方式去控制的,不难想象当我们初始化CountDownLatch的时候传入了一个int变量这个时候在类的内部初始化一个int的变量,每当我们调用countDownt()方法的时候就使得这个变量的值减1,而对于await()方法则去判断这个int的变量的值是否为0,是则表示所有的操作都已经完成,否则继续等待。
实际上如果了解AQS的话应该很容易想到可以使用AQS的共享式获取同步状态的方式来完成这个功能。而CountDownLatch实际上也就是这么做的。
从结构上来看CountDownLatch的实现还是很简单的,通过很常见的继承AQS的方式来完成自己的同步器。
CountDownLatch的同步器实现:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
//初始化state
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
//尝试获取同步状态
//只有当同步状态为0的时候返回大于0的数1
//同步状态不为0则返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//自旋+CAS的方式释放同步状态
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;
}
}
}
比较关键的地方是tryAquireShared()方法的实现,因为在父类的AQS中aquireShared()方法在调用tryAquireShared()方法的时候的判断依据是返回值是否大于零。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
//失败则进入等待队列
doAcquireShared(arg);
}
同步器的实现相对都比较简单,主要思路和上面基本一致。
CountDownLatch的主要方法(本身代码量就很少就直接贴了)
public class CountDownLatch {
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;
}
}
}
private final Sync sync;
//初始化一个同步器
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
//调用同步器的acquireSharedInterruptibly方法
//并且是响应中断的
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//调用同步器的releaseShared方法去让state减1
public void countDown() {
sync.releaseShared(1);
}
//获取剩余的count
public long getCount() {
return sync.getCount();
}
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
最后:由于CountDownLatch需要开发人员很明确需要等待的条件,否则很容易造成await()方法一直阻塞的情况。