在JDK并发包里提供了几个非常有用的并发工具类。CountDownLath、CylicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了线程间交换数据的一种手段。
CountDownLatch允许一个或多个线程等待其他线程完成操作。
假如我们有这样一个需求:我们需要解析一个Excel的多个sheet的数据,此时我们可以考虑使用多线程,每个线程解析一个sheet的数据,等到所有线程都解析完之后,程序需要提示解析完成。
在这个需求中,要实现主线程等待所有线程完成sheet的解析操作,最简单的做法是使用join()方法,如下:
/** * @author perist * @date 2017/3/19 * @time 18:00 */ public class JoinCountDownLatchDemo {
public static void main(String[] args) throws InterruptedException{ Thread parse1 = new Thread(new Runnable() { @Override public void run() { System.out.println("sheet 1 finished"); } }); Thread parse2 = new Thread(new Runnable() { @Override public void run() { System.out.println("sheet 2 finished"); } }); parse1.start(); parse2.start(); parse1.join(); parse2.join(); System.out.println("all finished "); }
}
join可以让当前执行线程等待join线程执行结束。其实现原理是不停的检查join线程是否存活,如果join线程存活则让当前线程永远等待,wait(0)表示永远等待。代码片段如下:
while (isAlive()) { wait(0); }
直到线程join终止后,线程的this.notifyAll()方法会被调用,调用notifyAll()方法是在JVM里实现的,所以在JDK中看不到。
在JDK中提供的CountDownLatch也可以实现join的功能,并且比join功能更多。
public class CountDownLatchDemo {
public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(3); CountDownLatchDemo countDownLatchDemo = new CountDownLatchDemo(); Thread first = new Thread(countDownLatchDemo.new Service("first", 4, countDownLatch)); Thread second = new Thread(countDownLatchDemo.new Service("second", 200, countDownLatch)); Thread third = new Thread(countDownLatchDemo.new Service("third", 110, countDownLatch)); first.setDaemon(true); first.start(); second.start(); third.start(); try { countDownLatch.await(); System.out.println("协同线程全部启动完毕。!"); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 线程服务类 */ class Service implements Runnable { /** * 别名 */ private final String name; /** * 线程等待时间 */ private final int timeToStart; /** * 计数 */ private final CountDownLatch countDownLatch; public Service(String name, int timeToStart, CountDownLatch countDownLatch) { this.name = name; this.timeToStart = timeToStart; this.countDownLatch = countDownLatch; } @Override public void run() { try { TimeUnit.SECONDS.sleep(timeToStart); System.out.println(name + ":执行完毕"); countDownLatch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } } }
}
CountDownLatch的构造函数接收一个int参数作为计数器,如果你想等待N个点完成,这里就传入N。
当我们调用CountDownLatch的countDown方法时,N就会减1,CountDownLatch的await方法会阻塞当前线程,直到N变成0。由于countDown方法可以用在任何地方,所以这里说的N个点,可以是N个线程,也可以是一个线程里面的N个步骤。用在多线程时,只需要把这个CountDownLatch传入到线程里即可。
一个线程调用countDown方法happen-before,另一个线程调用await方法。