在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方法。