在JDK的并发包里提供了几个非常有用的并发工具类,其中CountDownLatch、CyclicBarrier是最常用的两个。
首先介绍下各自的简单使用
1、闭锁CountDownLatch
顾名思义,CountDownLatch扮演的是一个类似计数器的角色,等待计数器为零,也俗称闭锁。CountDownLatch首先初始化一个计数器,await方法之前的线程每执行一次计数器减一,直到计数器执为零,就会放弃阻塞,await所有被屏障拦截的线程才会继续运行,看如下一段代码:
public class CountDownLatchTest {
public static int n=5;
public static CountDownLatch latch = new CountDownLatch(n);//初始化计数器
public static void main(String[] args) throws InterruptedException {
//创建n个线程
for (int i = 0; i < n; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(111111111);
latch.countDown();//每个线程执行完成,计数器减一
}
}).start();
}
latch.await();//当计数器大于零时,主线程等待;当计数器为零时,主线程继续执行
System.out.println(222222222);
}
}
当我们调用CountDownLatch的countDown方法时,计数器n就会减1,CountDownLatch的await方法判断计数器是否大于零,如果大于零就会阻塞当前线程,直到计数器变成零就不会再继续阻塞。由于countDown方法可以用在任何地方,所以这里说的n个点,可以是n个线程,也可以是1个线程里的n个执行步骤。用在多个线程时,只需要把这个CountDownLatch的引用传递到线程里即可。
2、栅栏CyclicBarrier
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行,强调线程间相互等待,扮演着栅栏的角色。简要分析如下代码:
public class CyclicBarrierTest {
public static CyclicBarrier barrier = new CyclicBarrier(2);
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
try {
barrier.await();//遇到屏障被阻塞
System.out.println(11111111);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
try {
barrier.await();//遇到屏障被阻塞
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(33333333);
}
}
CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。因为主线程和子线程的调度是由CPU决定的,两个线程都有可能先执行,所以会产生两种顺序的输出:1 2或者2 1
3、CyclicBarrier和CountDownLatch的区别
CountDownLatch一般用于一个线程等待其他若干线程完成后才执行,即一等多。
CyclicBarrier一般用于多个线程的相互等待,最后一起执行某种操作,即多个相互等。
所以,CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。
CountDownLatch计数器只能使用一次,不可重复使用。
CyclicBarrier的计数器可以使用reset()方法重置计数器,即重复使用。
所以CyclicBarrier能处理更为复杂的业务场景。例如,如果计算发生错误,可以重置计数器,并让线程重新执行一次。