CountDownLatch官方使用手册:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html
一、原理
CountDownLatch是一个非常实用的多线程控制工具类。Count Down在英文中意为倒计时,Latch意为门闩,可以简单的将CountDownLatch称为倒计时器。门闩的含义是:把门锁起来,不让里面的线程跑出来。因此,这个工具通常用来控制线程等待,它可以让一个线程等待知道倒计时结束,再开始执行。
CountDownLatch内部维护着一个count计数,只不过对这个计数器的操作都是原子操作,同时只能有一个线程去操作这个计数器。CountDownLatch通过构造函数传入一个初始计数值,调用者可以通过调用CounDownLatch对象的countDown()方法,来使计数减1;如果调用对象上的await()方法,那么调用者就会一直阻塞在这里,直到别人通过countDown方法,将计数减到0,才可以继续执行。
CountDownLatch的一种典型应用场景是火箭发射。在火箭发射前,为了保证万无一失,往往要进行各项设备、仪器的检查。只有等所有的检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程等待所有检查线程全部完成后再执行。
二、API
主要方法
- public CountDownLatch(int count);
构造方法参数指定了计数的次数
- public void countDown();
当前线程调用此方法,则计数减一
- public void await() throws InterruptedException
调用此方法会一直阻塞当前线程,直到计时器的值为0
三、Demo
CountDownLatch的一种典型用法是:
a group of worker threads use two countdown latches(一组工作线程使用两个CountDownLatch)。
Sample usage: a group of worker threads use two countdown latches(一组工作线程使用两个CountDownLatch):
- The first is a start signal that prevents any worker from proceeding until the driver is ready for them to proceed;
- The second is a completion signal that allows the driver to wait until all workers have completed
下面以一个Demo来说明CountDownLatch的使用。
程序功能:模拟多线程下载,合并线程等到所有下载任务完成便开始合并。
/** * CountDownLatch的典型用法2 * 代码功能:使用多个下载线程实现下载,等到所有任务完成后合并线程开始合并。 * * @author lp * */ public class CountDownLatchDemo2 { public static void main(String[] args) throws InterruptedException { int N = 8; CountDownLatch startSignal = new CountDownLatch(1);//startSignal控制开始 CountDownLatch doneSignal = new CountDownLatch(N);// ExecutorService executor = Executors.newFixedThreadPool(N + 1);// 8个下载线程,1个合并线程 // let all threads proceed startSignal.countDown(); // N个下载任务开始执行 for (int i = 0; i < N; ++i) { executor.execute(new DownloadRunnable(startSignal,doneSignal, i)); } // wait for all to finish doneSignal.await();//阻塞,直到计数器为0 // 执行合并任务 executor.execute(new MergeRunnable()); executor.shutdown(); } } /** * 下载线程 */ class DownloadRunnable implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; private final int i; public DownloadRunnable(CountDownLatch startSignal, CountDownLatch doneSignal, int i) { this.startSignal = startSignal; this.doneSignal = doneSignal; this.i = i; } public void run() { try { startSignal.await();//当前线程等待 doDownload(i); doneSignal.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } private void doDownload(int i) { System.out.println("Thead:" + i + "下载完成"); } }
Another typical usage would be to divide a problem into N parts, describe each part with a Runnable that executes that portion and counts down on the latch, and queue all the Runnables to an Executor. When all sub-parts are complete, the coordinating thread will be able to pass through await. (When threads must repeatedly count down in this way, instead use a CyclicBarrier
.)
另外一种典型的用法是:将一个问题分解成N部分,用Runnable来描述每一部分任务,每个Runnable执行完后将latch数减1,而且要将所有的Runnable都加入到Executor中。当所有子部分任务完成时,协调线程经过await后能够开始执行。(当线程必须可重复性的倒计时时,请使用CyclicBarrier代替)
/** * CountDownLatch的典型用法1 * 代码功能:模拟多线程下载功能-使用多个下载线程实现下载,等到所有任务完成后合并线程开始合并。 * * @author lp * */ public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { int N = 8; CountDownLatch doneSignal = new CountDownLatch(N);// 计数器从N开始倒数 ExecutorService executor = Executors.newFixedThreadPool(N + 1);// N个下载线程,1个合并线程 // N个下载任务开始执行 for (int i = 0; i < N; ++i) { executor.execute(new DownloadRunnable(doneSignal, i)); } // wait for all to finish doneSignal.await();//阻塞,直到计数器为0 // 执行合并任务 executor.execute(new MergeRunnable()); executor.shutdown(); } } /** * 下载线程 */ class DownloadRunnable implements Runnable { private final CountDownLatch doneSignal; private final int i; DownloadRunnable(CountDownLatch doneSignal, int i) { this.doneSignal = doneSignal; this.i = i; } public void run() { doDownload(i); doneSignal.countDown(); } private void doDownload(int i) { System.out.println("Thead:" + i + "下载完成"); } } /** * 合并线程 */ class MergeRunnable implements Runnable{ @Override public void run() { doMerge(); } private void doMerge(){ System.out.println("合并线程完成合并"); } }