一、倒计数器:CountDownLatch
CountDownLatch 是一个非常实用的多线程控制工具类。“Count Down” 在英文中意为倒计数,Latch 意为门闩的意思。如果翻译成为倒计数门闩,我想大家都会不知所云吧!因此,这里简单地称之为倒计数器。在这里,门闩的含义是把门锁起来,不让里面的线程跑出来。因此,这个工具通常用来控制线程等待,它可以让某一个线程等待直到倒计数结束,再开始执行。
对于倒计数器,一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要对各项设备、仪器进行检查。只有等所有检查都完成后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使点火线程等待所有检查线程全部完工后再执行。CountDownLatch 的构造函数接收一个整数作为参数,即当前这个计数器的计数个数。
注意这里的等待也就是end.await();
,也就是计数达到之后,就相当于执行力 end.signal
public CountDownLatch(int count)
下面这个简单的示例,演示了CountDownLatch的使用方法。
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo implements Runnable {
//计数数量为10表示需要10个线程完成任务后等待在 CountDownLatch 上的线程才能继续执行
static final CountDownLatch end = new CountDownLatch(10);
static final CountDownLatchDemo demo = new CountDownLatchDemo();
@Override
public void run() {
try {
//模拟检查任务
Thread.sleep(new Random().nextInt(10) * 1000);
System.out.println("check complete!");
//通知CountDownLatch,一个线程 已经完成任务,倒计数器减1
end.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
exec.submit(demo);
}
//主线程等待所有检查任务全部完成,等待10个任务全部完成后,主线程才能继续执行,也就是会解除 await
end.await();
System.out.println("Fire!");
exec.shutdown();
}
}
上述案例的执行逻辑可以用图3.1简单表示。
主线程在CountDownLatch上等待,当所有检查任务全部完成后,主线程方能继续执行。
二、循环栅栏:CyclicBarrier
CyclicBarrier 是另外一种多线程并发控制工具。和 CountDownLatch 非常类似,它也可以实现线程间的计数等待,但它的功能比 CountDownLatch 更加复杂且强大。
CyclicBarrier可以理解为循环栅栏。栅栏就是一种障碍物,比如,通常在私人宅邸的周围就可以围上一圈栅栏,阻止闲杂人等入内。这里当然就是用来阻止线程继续执行,要求线程在栅栏外等待。前面Cyclic意为循环,也就是说这个计数器可以反复使用。比如,我们将计数器设置为10,那么凑齐第一批10个线程后,计数器就会归零,接着凑齐下一批10个线程,这就是循环栅栏内在的含义。
CyclicBarrier 的使用场景也很丰富。比如,司令下达命令,要求10个士兵一起去完成一项任务。这时就会要求10个士兵先集合报到,接着,一起雄赳赳,气昂昂地去执行任务。当10个士兵把自己手上的任务都执行完了,那么司令才能对外宣布,任务完成!
CyclicBarrier 比CountDownLatch 略微强大一些,它可以接收一个参数作为barrierAction。所谓barrierAction就是当计数器一次计数完成后,系统会执行的动作。如下构造函数,其中,parties表示计数总数,也就是参与的线程总数。
public CyclicBarrier{int parties,Runnable barrierAction}
下面的示例使用CyclicBarrier演示了上述司令命令士兵完成任务的场景。
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static class Soldier implements Runnable {
private String soldier;
private final CyclicBarrier cyclic;
Soldier(CyclicBarrier cyclic, String soldier) {
this.cyclic = cyclic;
this.soldier = soldier;
}
@Override
public void run() {
try {
//等待所有士兵到齐,也就是当第一个计数达到后解除等待
cyclic.await();
doWork();
//等待所有士兵完成工作,也就是第二个计数达到后解除等待
cyclic.await();
} catch (InterruptedException e) {//处理异常
e.printStackTrace();
} catch (BrokenBarrierException e) {//处理异常
e.printStackTrace();
}
}
public void doWork() {
try {
Thread.sleep(Math.abs(new Random().nextInt() % 10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(soldier + "任务完成");
}
}
public static class BarrierRun implements Runnable {
boolean flag;
int N;
BarrierRun(boolean flag, int N) {
this.flag = flag;
this.N = N;
}
@Override
public void run() {
if (flag) {
System.out.println("司令:【士兵" + N + "个,任务完成!】");
} else {
System.out.println("司令:【士兵" + N + "个,集合完毕!】");
flag = true;
}
}
}
public static void main(String[] args) {
final int N = 5;
Thread[] allSoldiers = new Thread[N];
boolean flag = false;
//创建CyclicBarrier实例,同时将计数器设置为5,要求在计数器达到指标时执行BarrierRun下的run()方法
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
System.out.println("集合队伍");
for (int i = 0; i < N; i++) {
System.out.println("士兵" + i + "报道");
allSoldiers[i] = new Thread(new Soldier(cyclic, "士兵" + i));
allSoldiers[i].start();
}
}
}
每一个士兵线程都会执行第13行定义的run()方法。
在第16行,每一个士兵线程都会等待,直到所有的士兵都集合完毕。
集合完毕意味着CyclicBarrier的一次计数完成,当再一次调用CyclicBarrier.await()
方法时,会进行下一次计数。
第30行模拟了士兵的任务。当一个士兵任务执行完,他就会要求CyclicBarrier开始下一次计数,这次计数主要目的是监控是否所有的士兵都已经完成了任务。一旦任务全部完成,第40行定义的BarrierRun就会被调用,打印相关信息。
CyclicBarrier.await()
方法可能会抛出两个异常。一个是InterruptedException
,也就是在等待过程中,线程被中断,应该说这是一个非常通用的异常。大部分迫使线程等待的方法都可能会抛出这个异常,使得线程在等待时依然可以响应外部紧急事件。
另外一个异常则是CyclicBarrier特有的BrokenBarrierException
。一旦遇到这个异常,则表示当前的CyclicBarrier已经破损了,可能系统已经没有办法等待所有线程到齐了。如果继续等待,可能就是徒劳无功的,因此,还是“打道回府”吧!上述代码第23~27行处理了这两种异常。