一、CountDownLatch
在开发环境中会有这么一个场景:在主线程中开启多个线程并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总。
直接手动创建线程:
public class JoinCountDownLatch1 {
// 创建一个CountDownLatch实例
private static volatile CountDownLatch countDownLatch
= new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 对计数器-1
countDownLatch.countDown();
}
System.out.println("child thread1 done!");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 对计数器-1
countDownLatch.countDown();
}
System.out.println("child thread2 done!");
}
});
// 启动子线程
thread1.start();
thread2.start();
System.out.println("wait all child thread over!");
// 阻塞等待所有子线程运行完毕(countDownLatch的计数器减为0)
countDownLatch.await();
System.out.println("all child thread over!");
}
}
使用线程池对线程进行管理
在项目实践中一般避免直接操作线程,而是使用ExecutorService线程池来管理。使用ExecutorService时传递的参数是Runnable或者Callable对象,此时无法直接调用这些线程的join()方法,这就需要使用CountDownLatch达到效果了:
public class JoinCountDownLatch2 {
// 创建一个CountDownLatch实例
private static volatile CountDownLatch countDownLatch =
new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
// 将线程1加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
} catch (InterruptedException ex){
ex.printStackTrace();
} finally {
countDownLatch.countDown();
}
System.out.println("child thread1 done!");
}
});
// 将线程2加到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
} catch (InterruptedException ex){
ex.printStackTrace();
} finally {
countDownLatch.countDown();
}
System.out.println("child thread2 done!");
}
});
System.out.println("wait all child thread over");
// 阻塞等待子线程执行完毕
countDownLatch.await();
System.out.println("all child thread done!");
// 关闭线程池
executorService.shutdown();
}
}
和join()的区别:
- 调用一个子线程的join()方法后,该线程会一直阻塞直到子线程运行完毕,而CountDownLatch使用计数器来运行子线程运行完毕或者在运行中递减计数,也就是CountDownLatch可以在子线程运行的任何时候让await()方法返回而不一定必须等到线程结束
- 使用线程池来管理线程时一般都是添加Runnable/Callable到线程池,这时候就无法再调用join方法
- 总的来说,就是CountDownLatch相比join方法对线程同步有更灵活的控制。
CountDownLatch原理概述:
CountDownLatch使用AQS实现,使用AQS的状态变量来存放计数器的值。首先在初始化CountDownLatch时设置状态值(计数器值),当多个线程调用countDown()时实际是原子性递减AQS的状态值。当线程调用await()后当前线程会被放入AQS的阻塞队列等待计数器为0再返回。其他线程调用countDown()让计数器-1,当计数器值变为0的时候,当前线程还要调用AQS的doReleaseShared方法来激活由于调用await()而被阻塞的线程。