1、案例介绍
在日常开发中经常会遇到需要在线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。在CountDownLatch出现之前一般都使用线程的join()方法来实现这一点,但是join()方法不够灵活,不能够满足不同场景的需要。
使用CountDownLatch的代码如下:
public class JoinCountDownLatch {
// 创建一个CountDownLatch实例
private static volatile CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
System.out.println("child threadOne over");
}
});
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
System.out.println("child threadTwo over");
}
});
// 启动子线程
threadOne.start();
threadTwo.start();
System.out.println("wait all child thread over");
// 等待子线程执行完毕,返回
countDownLatch.await();
System.out.println("all child thread over");
}
}
如上代码创建了一个CountDownLatch实例,因为有两个子线程所以构造函数的传参为2。主线程调用countDownLatch.await()方法后被阻塞。子线程执行完毕后调用countDownLatch.countDown方法让countDowunLatch内部计数器减1,所有子线程执行 完毕并调用countDown()方法后计数器变为0,这时候主线程的await()方法才会返回。
代码优化:
在项目实践中一般都避免直接操作线程,而是使用ExecutorService线程池来管理。使用ExecutorService时传递的参数是Runable或者Callable对象,这时候你没有办法直接调用线程的join()方法,这就需要使用CountDownLatch了。
public class JoinCountDownLatch2 {
// 创建一个CountDownLatch实例
private static CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 将线程A添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
System.out.println("child threadOne lover");
}
});
// 将线程B添加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
}
System.out.println("child threadTwo over");
}
});
System.out.println("wait all child thread over");
// 等待子线程执行完毕,返回
countDownLatch.await();
System.out.println("all child thread over");
executorService.shutdown();
}
}
CountDownLatch与join方法的区别:
调用一个子线程的join()方法后,该线程会一直被阻塞到子线程运行完毕,而CountDowunLatch则使用计数器来允许子线程运行完毕或者在运行中递减计数,也就是CountDownLatch可以在子线程运行的任何时候让await方法返回而不一定必须等到线程结束。
使用线程池来管理啊线程时一般都是直接添加Runable到线程池,这时候就没有办法再调用线程的join方法了,就是说countDownLatch相比join方法让我们对线程同步有更灵活的控制。
2、主要方法
2.1、void await()方法
当线程调用CountDownLatch对象的await方法后,当前线程会被阻塞,直到下面的情况之一发生才会返回:
当所有线程都调用了CountDownLatch对象的countDown方法后,也就是计数器的值为0时。
其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程就会抛出InterruptedException异常,然后返回。
2.2、boolean await(long timeout, TimeUnit unit)方法
当线程调用了CountDownLatch对象的方法后,当前线线程会被阻塞,直到下面的情况之一发生才会返回:
当所有线程都调用了CountDownLatch对象的countDown方法后,也就是计数器值为0时,这时候会返回true。
设置的timeout时间到了,因为超时而返回false。
其他线程调用了当前线程的interrupt()方法中断了当前线程,当前线程会抛出InterruptedException异常,然后返回。
2.3、void countDown()方法
线程调用该方法后,计数器的值递减,递减后如果计数器值为0则唤醒所有因调用await方法而被阻塞的线程,否则什么都不做。
2.4、long getCount()方法
获取当前计数器的值,也就是AQS的state的值,一般再测试时使用该方法。
3、总结
CountDownLatch相比使用join方法来实现线程间的同步更具灵活性和方便性。CountDownLatch使用AQS实现。使用AQS的状态值来存放计数器的值。首先在初始化CountDownLatch时设置状态值(计数器值),当多个线程调用countdown方法时实际时原子性递减AQS的状态值。当线程调用await方法后当前线程会被放入AQS的阻塞队列等待计数器为0再返回。其他线程调用countdown方法让计数器递减1,当计数器值变为0时,当前线程还要调用AQS的doReleaseShared方法来激活由于调用await()方法而被阻塞的线程。