一、CountDownLatch
CountDownLatch是拦截线程等待事件发生,提供了一个countDown方法来操作计数器的值,每调用一次countDown方法计数器都会减1,直到计数器的值减为0时就代表条件已成熟,所有因调用await方法而阻塞的线程都会被唤醒。
1、下面是java源码的两个demo:
(1)
/**
*
* startSignal:司机准备好之前,不允许工人上车;
* doneSignal:司机需要等待所有工人完成工作
*/
class Driver {
void main() throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);// 开始信号
CountDownLatch doneSignal = new CountDownLatch(10);// 结束信号
for (int i = 0; i < 10; ++i) {
Thread d = new Thread(new Worker(startSignal, doneSignal));
d.setName("工人"+i);
d.start();
}
System.out.println("检修、发动大汽车");
startSignal.countDown(); //司机准备
System.out.println("等待工人..");
doneSignal.await(); //等待工人
System.out.println("出发了...");
}
}
class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await();//等待司机准备完成
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {
}
}
void doWork() {
System.out.println(Thread.currentThread().getName()+"工作");
}
}
(2)
/**
*
*另一种常用方式是将一个问题分解成多个部分,每个部分用一个线程表示,并且执行这个部分进行countdown操作,将这些部分都提交到executor执行
*
*
*/
class Driver2 {
private static final int N = 5;
void main() throws InterruptedException {
CountDownLatch doneSignal = new CountDownLatch(N);
Executor e = Executors.newFixedThreadPool(N);
for (int i = 0; i < N; ++i) // create and start threads
e.execute(new WorkerRunnable(doneSignal, i));
doneSignal.await(); // wait for all to finish
System.out.println("======完成======");
}
}
class WorkerRunnable implements Runnable {
private final CountDownLatch doneSignal;
private final int i;
WorkerRunnable(CountDownLatch doneSignal, int i) {
this.doneSignal = doneSignal;
this.i = i;
}
public void run() {
doWork(i);
doneSignal.countDown();
}
void doWork(int i) {
System.out.println("执行第"+i+"部分");
}
}
2、解析
由上面示例可以看到,CountDownLatch主要是通过await()和countDown()进行操作的。来看看源码是如何实现的。
CountDownLatch的构造器需要传入一个计数作为参数,并且实例化了Sync对象,可以看到实例化Sync时,Sync的构造器将同步状态state设置为传入的参数count
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
CountDownLatch的实现依赖于AQS,它也是通过内部类继承AQS重写了一些主要的方法
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
//获取共享锁
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;//如果同步状态为0,获取成功,返回1,否则获取失败
}
//释放共享锁
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();//获取同步状态
if (c == 0)//如果同步状态为0, 则不能再释放了
return false;
int nextc = c-1;//否则,获取数量减1
if (compareAndSetState(c, nextc))//更新同步状态
return nextc == 0;//当同步状态为0时,释放成功
}
}
}
(1)await()方法
可以看到方法内部调用了AQS的acquireSharedInterruptibly方法,该方法会调用子类的tryAcquireShared方法,如果tryAcquireShared返回的值小于0,就会调用doAcquireSharedInterruptibly将线程插入同步队列中等待。
如果调用tryAcquireShared返回的值为1(也就是countdown使得state==0),获取成功,就会继续进行,失败则将调用await方法的线程放入同步队列。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
(2)countDown()方法
countdown调用了AQS的releaseShared,实际上执行的是一个释放锁的操作,父类会调用子类的tryReleaseShared。
如果state为0,唤醒调用await方法被阻塞的线程。
public void countDown() {
sync.releaseShared(1);
}
二、CyclicBarrier(部分摘自Java并发系列[8]----CyclicBarrier源码分析 作者:劳夫子)
利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。在CyclicBarrier类的内部有一个计数器,每个线程在到达屏障点的时候都会调用await方法将自己阻塞,此时计数器会减1,当计数器减为0的时候所有因调用await方法而被阻塞的线程将被唤醒。
1、先看一下CyclicBarrier的几个全局变量
/**
* 每一次使用栅栏代表一代.
* 栅栏换代时generation也会改变或者重置
*/
private static class Generation {
boolean broken = false;
}
/**栅栏进入同步锁 */
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
private final Condition trip = lock.newCondition();
/** 每次拦截的线程数 */
private final int parties;
/* 换代前执行的任务 */
private final Runnable barrierCommand;
/** 栅栏当前代 */
private Generation generation = new Generation();
/**
* 计数器。Number of parties still waiting. Counts down from parties to 0
* on each generation. It is reset to parties on each new
* generation or when broken.
*/
private int count;
parties表示每次拦截的线程数,该值在构造时进行赋值。count是内部计数器,它的初始值和parties相同,以后随着每次await方法的调用而减1,直到减为0就将所有线程唤醒。可以看到CyclicBarrier的构造器:
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;//count初始值为parties
this.barrierCommand = barrierAction;
}
CyclicBarrier有一个静态内部类Generation,该类的对象代表栅栏的当前代,就像玩游戏时代表的本局游戏,利用它可以实现循环等待。barrierCommand表示换代前执行的任务,当count减为0时表示本局游戏结束,需要转到下一局。在转到下一局游戏之前会将所有阻塞的线程唤醒,在唤醒所有线程之前你可以通过指定barrierCommand来执行自己的任务。
2、核心方法
在CyclicBarrier中实现了主要功能的方法是await()
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
//1.检查栅栏是否被打破
if (g.broken)
throw new BrokenBarrierException();
//2.如果线程被中断了,打破栅栏
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;//3.剩余等待数量-1
if (index == 0) { // 4.1数量为0时,进行换代
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();//换代前执行任务
ranAction = true;
nextGeneration();//换代
return 0;
} finally {//finally语句会在try的return语句执行之后,return返回之前执行
//确保在任务未成功执行时能将所有线程唤醒
if (!ranAction)
breakBarrier();
}
}
// 4.2 剩余等待数量不为0时。一直循环直到换代、被打破、被中断或者时间到了
for (;;) {
try {
//根据传入的参数来决定是定时等待还是非定时等待
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//若当前线程在等待期间被中断则打翻栅栏唤醒其他线程
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
/** 5.线程醒来后会执行下面三个判断,看看是否因为调用breakBarrier方法而被唤醒,如果是则抛出异常;看看是否是正常的换代操作而被唤醒,如果是则返回计数器的值;看看是否因为超时而被唤醒,如果是的话就调用breakBarrier打破栅栏并抛出异常。这里还需要注意的是,如果其中有一个线程因为等待超时而退出,那么整盘游戏也会结束,其他线程都会被唤醒。**/
//如果线程因为打翻栅栏操作而被唤醒则抛出异常
if (g.broken)
throw new BrokenBarrierException();
//如果线程因为换代操作而被唤醒则返回计数器的值
if (g != generation)
return index;
//如果线程因为时间到了而被唤醒则打翻栅栏并抛出异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
/**
* 设置栅栏当前代被打破,唤醒所有线程.
* 该方法只会在持有锁时被调用
*/
private void breakBarrier() {
generation.broken = true;//更新broken标志
count = parties;//重置count为parties
trip.signalAll();//唤醒所有线程
}
/** 切换栅栏到下一代 */
private void nextGeneration() {
trip.signalAll();//已经完成,唤醒拦截的所有线程
// set up next generation
count = parties;//重置count的值为parties(需要拦截的线程数)
generation = new Generation();//新建一代
}
三、总结(摘自Java并发系列[8]----CyclicBarrier源码分析 作者:劳夫子)
CountDownLatch只能拦截一轮,而CyclicBarrier可以实现循环拦截。CyclicBarrier的计数器由自己控制,而CountDownLatch的计数器则由使用者来控制,在CyclicBarrier中线程调用await方法不仅会将自己阻塞还会将计数器减1,而在CountDownLatch中线程调用await方法只是将自己阻塞而不会减少计数器的值。一般来说用CyclicBarrier可以实现CountDownLatch的功能,而反之则不能。