1、背景
在JDK1.5之后加入了concurrent包,增强了Java在并发编程方面的表现。减轻了程序员的负担,使得程序员在使用传统的 wait(),notify()和synchronized等之外,可以选择更加便利更加易用的concurrent包中的类,减轻了程序员的负担。 concurrent包的作者是大名鼎鼎的Doug Lea,他对称之为世界上对Java影响力最大的个人,一个非常有趣的老大爷。
2、简介
concurrent包中包含一个用于显示锁机制的Locks子包和用于原子操作的Atomic子包以及常用的多线程类。在多线程处理 中,concurrent提供了线程池机制用来代替程序员手动创建一个线程。这样可以提高资源的利用率,提高程序的运行效率。同时,在 concurrent包中也提供了用于线程同步的阻塞队列,有兴趣的读者可以去了解下。同时,在concurrent包中存在两个对多线程处理已经封装好 的辅助类,分别为CountDownLacth和CyclicBarrier。这就是我们今天讨论的主题。
3、CountDownLatch和CyclicBarrier的使用
通过查看JDK等帮助文档可以初步了解到,这两个类都是在初始化时提供一个数字用于表明需要等待的线程数。不同的是,CyclicBarrier类的另一 个初始化方法可以指定一个实现Runnable接口的类,它在所有线程都同步到时执行一次。通过JDK文档可以看到,CountDownLatch和 CyclicBarrier的区别在于CyclicBarrier可以复用而CountDownLatch却不可以复用。
下面我们来看一下CountDownLatch和CyclicBarrier的基本使用方法:
//CountDownLatch使用
public class TestFirst {
public static void main(String[] args) throws InterruptedException {
new Driver().main();
}
static class Driver {
static final int N = 10;
void main() throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
CountDownLatch startDoor = new CountDownLatch(1); // 建立一个门
CountDownLatch doneDoor = new CountDownLatch(N); // 建立10个门
for (int i = 0; i < N; ++i) {
exec.execute(new Worker(startDoor, doneDoor));
}
startDoor.countDown(); // 开门
doneDoor.await(); // 探门。门开则通过,门没开则站在那里等到开门....
System.out.println("Worker,开门咯...");
exec.shutdown();
}
}
static class Worker implements Runnable {
private final CountDownLatch startDoor;
private final CountDownLatch doneDoor;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startDoor = startSignal;
this.doneDoor = doneSignal;
}
public void run() {
try {
startDoor.await(); // 探门。门开则通过,门没开则站在那里等到开门....
System.out.println("Driver,开门咯...");
doWork();
doneDoor.countDown(); // 开门
} catch (InterruptedException ex) {
}
}
void doWork() {
}
}
}
运行结果是:
Driver,开门咯...
Driver,开门咯...
Driver,开门咯...
Driver,开门咯...
Driver,开门咯...
Driver,开门咯...
Driver,开门咯...
Driver,开门咯...
Driver,开门咯...
Driver,开门咯...
Worker,开门咯...
再来看看CyclicBarrier类的使用:
public class CyclicBarrierStudy {
static final int SIZE = 5;
static final int result[] = new int[] { 0, 0, 0, 0, 0 };
static final List<WorkerBarrier> workerList = new ArrayList<WorkerBarrier>();
static final ExecutorService exec = Executors.newCachedThreadPool();
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(SIZE, new Runnable() {
@Override
public void run() {
for (int k = 0; k < SIZE; k++) {
if (result[k] > 100) {
System.out.println("the " + k + " is the big!"
+ " and the result is " + result[k]);
exec.shutdownNow();
break;
}
}
}
});
for (int i = 0; i < SIZE; i++) {
WorkerBarrier workerBarrier = new WorkerBarrier(cyclicBarrier, i);
exec.execute(workerBarrier);
}
}
}
class WorkerBarrier implements Runnable {
private final CyclicBarrier barrier;
private final int runId;
private final static Random random = new Random();
public WorkerBarrier(CyclicBarrier barrier, int id) {
this.barrier = barrier;
runId = id;
}
@Override
public void run() {
while (!Thread.interrupted()) {
CyclicBarrierStudy.result[runId] += random.nextInt(5) + 2;
try {
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
System.out.println("is break;");
break;
}
}
}
}
运行结果(由于多线程竞争的原因,导致运行的结果和下面会不一样):
the 3 is the big! and the result is 102
4、CountDownLatch和CyclicBarrier异同点分析
从上述代码可以看出,好像CountDownLatch和CyclicBarrier在使用上没有多少差异。它们都可以实现了多个线程同时运行并等待初始 值到达零然后继续运行的情况。唯一不同的就是CyclicBarrier可以在到达零时运行一个事先指定的实现Runnable接口的类。然而,实际上这 两个类在设计的目的上是有很大的差异的。首先我们来看CountDownLatch和CyclicBarrier的英文帮助文档描述:
CountDownLatch的描述为:
A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.
CyclicBarrier的描述为:
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.从描述中我们就可以看到一点端倪,CountDownLatch的作用是让一个或者多个线程阻塞去等待一个或者多个操作完成,然后使自己的线 程得以通过然后运行。而CyclicBarrier的作用是是让一组有共同特性的线程相互等待直到到达一个公共点然后经过部分处理继续运行。可以打一个比喻,就像我上面代 码注释说的,CountDownLatch就相当于一个关卡,这个关卡可以有一扇或者多扇门,并且同时它拥有一个(多个)开门者和一个(多个)探门者(并且这个探门者在门关闭时不会离 开,会一直等待开门者打开所有的门然后进入)。上面的例子中,Driver类和Worker类即做开门者也做探门者。对于startDoor这个关卡,它拥有一扇 门和十个探门者(Worker),探门者通过await方法探门(注意,不是wait方法,它们有很大区别)并一直等待开门者使用countDown打开门。这里需要注意的是,对于关卡拥有多个门时,需要等到所有门都打开时才能让探门者通过,即countDown的次数等于新建关卡的指定的数字。同样对于doneDoor这个关卡,它就相当于10扇门,有十个开门者(Worker)和一个探门者 (Driver)。只有10扇门都打开了,这一个探门者才能通过。对于有多个开门者,多个探门者的情况读者可以自行模拟。
而对于CyclicBarrier,它的作用就比较好解释。他主要用于处理一项比较庞大但是可以的分割的任务上,在使用时把这项大型的任务分割给多个模块并各自处理。在处理结束时,再把各个分结果组装为最终的总结果达到所要的完成的目标。从上述的例子可以看出来。
通过上述分析可以看到,CyclicBarrier适合于各个子任务可以有相互联系或者最终结果需要分结果进行组装的情况,而CountDownLatch则是对应于线程需要等待事件触发或特定操作发生的情景。
对于CountDownLatch和CyclicBarrier的内部实现机制如AQS同步器和CLH队列,我会在后面的文章中进行分析。