1. CountDownLatch
1.1 简介
CountDownLatch是一个同步辅助类,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。CountDownLatch用一个给定的计数器来初始化,该计数器的操作是原子操作,即同时只能有一个线程去操作该计数器。调用该类await方法的线程会一直处于阻塞状态,直到其他线程调用countDown方法使当前计数器的值变为零,每次调用countDown计数器的值减1。当计数器值减至零时,所有因调用await()方法而处于等待状态的线程就会继续往下执行。这种现象只会出现一次,因为计数器不能被重置,如果业务上需要一个可以重置计数次数的版本,可以考虑使用CycliBarrier。
1.2 API
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
用指定的值初始化计数器。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
调用该方法的线程进入等待状态,直到计数器的值减至0或者该线程被其他线程Interrupted。
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
调用该方法的线程进入等待状态,直到计数器的值减至0或者该线程被其他线程Interrupted或者等待时间超过指定的时间。
public void countDown() {
sync.releaseShared(1);
}
减少计数器 当前的值,每次调用值减少1。
public long getCount() {
return sync.getCount();
}
获取计数器 当前的值
1.3 使用场景
在某些业务场景中,程序执行需要等待某个条件完成后才能继续执行后续的操作;典型的应用如并行计算,当某个处理的运算量很大时,可以将该运算任务拆分成多个子任务,等待所有的子任务都完成之后,父任务再拿到所有子任务的运算结果进行汇总。
1.4 实际案例
子任务类:
public class Task implements Runnable {
private String taskCode;
private AtomicInteger count;
private CountDownLatch ready_latch;
private CountDownLatch begin_latch;
private CountDownLatch end_latch;
public Task(String taskCode, CountDownLatch ready_latch, CountDownLatch begin_latch,
CountDownLatch end_latch, AtomicInteger count) {
super();
this.taskCode = taskCode;
this.begin_latch = begin_latch;
this.end_latch = end_latch;
this.ready_latch = ready_latch;
this.count = count;
}
public void run() {
try {
Thread.sleep(1000);
System.out.println("子线程: 任务" + taskCode + "准备就绪。。。");
ready_latch.countDown();//已准备的任务+1
begin_latch.await();//等待开始信号
System.out.println("子线程: 任务" + taskCode + "开始执行。。。");
count.addAndGet(Integer.valueOf(taskCode));
Thread.sleep(1000);
System.out.println("子线程: 任务" + taskCode + "执行完成。。。");
} catch (InterruptedException e) {
e.printStackTrace();
} finally{
end_latch.countDown();//已完成的任务+1
}
}
}
public class TestMain {
public static void main(String[] args) throws InterruptedException {
AtomicInteger count = new AtomicInteger(0);
int taskNum = 10;
CountDownLatch ready_latch = new CountDownLatch(taskNum);
CountDownLatch begin_latch = new CountDownLatch(1);
CountDownLatch end_latch = new CountDownLatch(taskNum);
Executor executor = Executors.newCachedThreadPool();
System.out.println("主线程--> 开始分发任务。。。");
for(int i=1; i<=taskNum; i++){
Task task = new Task(String.valueOf(i), ready_latch, begin_latch, end_latch, count);
executor.execute(task);
}
System.out.println("主线程--> 等待所有子任务准备就绪。。。");
ready_latch.await();
System.out.println("主线程--> 所有子任务已准备就绪,通知子任务执行。。。");
begin_latch.countDown();
end_latch.await();
System.out.println("主线程--> 所有子任务执行完毕,获得结果:"+count.get());
}
}
执行结果:
主线程--> 开始分发任务。。。
主线程--> 等待所有子任务准备就绪。。。
子线程: 任务1准备就绪。。。
子线程: 任务3准备就绪。。。
子线程: 任务6准备就绪。。。
子线程: 任务7准备就绪。。。
子线程: 任务5准备就绪。。。
子线程: 任务9准备就绪。。。
子线程: 任务10准备就绪。。。
子线程: 任务8准备就绪。。。
子线程: 任务4准备就绪。。。
子线程: 任务2准备就绪。。。
主线程--> 所有子任务已准备就绪,通知子任务执行。。。
子线程: 任务1开始执行。。。
子线程: 任务9开始执行。。。
子线程: 任务4开始执行。。。
子线程: 任务5开始执行。。。
子线程: 任务7开始执行。。。
子线程: 任务6开始执行。。。
子线程: 任务3开始执行。。。
子线程: 任务2开始执行。。。
子线程: 任务8开始执行。。。
子线程: 任务10开始执行。。。
子线程: 任务10执行完成。。。
子线程: 任务7执行完成。。。
子线程: 任务2执行完成。。。
子线程: 任务5执行完成。。。
子线程: 任务6执行完成。。。
子线程: 任务3执行完成。。。
子线程: 任务8执行完成。。。
子线程: 任务1执行完成。。。
子线程: 任务4执行完成。。。
子线程: 任务9执行完成。。。
主线程--> 所有子任务执行完毕,获得结果:55
2. CyclicBarrier
2.1 简介
CyclicBarrier也是一个同步辅助类,它允许一组线程相互等待,直到到达某个公共屏障点(common barrier point)。通过它可以完成多个线程之间相互等待,只有当每个线程都准备就绪后,才能各自继续往下执行后面的操作。类似于CountDownLatch,它也是通过计数器来实现的。当某个线程调用await方法时,该线程进入等待状态,且计数器加1,当计数器的值达到设置的初始值时,所有因调用await进入等待状态的线程被唤醒,继续执行后续操作。因为CycliBarrier在释放等待线程后可以重用,所以称为循环barrier。CycliBarrier支持一个可选的Runnable,在计数器的值到达设定值后(但在释放所有线程之前),该Runnable运行一次,注,Runnable在每个屏障点只运行一个。
2.2 API
<span style="font-size:14px;"> public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}</span>
用指定值和Runnable初始化CyclicBarrier
public CyclicBarrier(int parties) {
this(parties, null);
}
用指定值初始化CyclicBarrier
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen;
}
}
调用该方法的线程进入等待状态,并且计数器加1,直到调用该方法的线程数达到设置值后或该线程被其他Interrputed
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
调用该方法的线程进入等待状态,并且计数器加1,直到调用该方法的线程数达到设置值后或该线程被其他Interrputed或者等待时间超过指定时间。
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
判断该Barrier是否处于broker状态
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
重置barrier进入初始状态。
2.3 使用场景
使用场景类似于CountDownLatch
2.4 与CountDownLatch的区别
- CountDownLatch主要是实现了1个或N个线程需要等待其他线程完成某项操作之后才能继续往下执行操作,描述的是1个线程或N个线程等待其他线程的关系。CyclicBarrier主要是实现了多个线程之间相互等待,直到所有的线程都满足了条件之后各自才能继续执行后续的操作,描述的多个线程内部相互等待的关系。
- CountDownLatch是一次性的,而CyclicBarrier则可以被重置而重复使用。
2.5 实际案例
<span style="font-size:14px;">public class Task implements Runnable{
private String taskCode;
private AtomicInteger count;
private CyclicBarrier begin_cyclicBarrier;
private CyclicBarrier end_cyclicBarrier;
public Task(String taskCode, AtomicInteger count,
CyclicBarrier begin_cyclicBarrier, CyclicBarrier end_cyclicBarrier) {
super();
this.taskCode = taskCode;
this.count = count;
this.begin_cyclicBarrier = begin_cyclicBarrier;
this.end_cyclicBarrier = end_cyclicBarrier;
}
@Override
public void run() {
try{
System.out.println("子线程: 子任务"+taskCode+"已准备就绪,等待其他子任务就绪。。。");
begin_cyclicBarrier.await();
System.out.println("子线程: 子任务"+taskCode+"开始执行");
Thread.sleep(1000);
count.addAndGet(Integer.valueOf(taskCode));
System.out.println("子线程: 子任务"+taskCode+"执行完成");
end_cyclicBarrier.await();
}catch(Exception e){
}
}
}</span>
<span style="font-size:14px;">public class ParentTask implements Runnable {
private String msg;
public ParentTask(String msg) {
super();
this.msg = msg;
}
@Override
public void run() {
System.out.println(msg);
}
}</span>
<span style="font-size:14px;">public class TestMain {
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
AtomicInteger count = new AtomicInteger(0);
int taskNum = 10;
CyclicBarrier begin_cBarrier = new CyclicBarrier(11, new ParentTask("主线程--> 所有子任务已准备就绪。。。"));
CyclicBarrier end_cBarrier = new CyclicBarrier(11, new ParentTask("主线程--> 所有子任务已执行完成。。。"));
Executor executor = Executors.newCachedThreadPool();
System.out.println("主线程--> 开始分发任务。。。");
for(int i=1; i<=taskNum; i++){
Task task = new Task(String.valueOf(i), count, begin_cBarrier, end_cBarrier);
executor.execute(task);
}
begin_cBarrier.await();
end_cBarrier.await();
System.out.println("主线程--> 所有子任务执行完毕,获得结果:"+count.get());
}
}</span>
执行结果:
<span style="font-size:14px;">主线程--> 开始分发任务。。。
子线程: 子任务1已准备就绪,等待其他子任务就绪。。。
子线程: 子任务2已准备就绪,等待其他子任务就绪。。。
子线程: 子任务3已准备就绪,等待其他子任务就绪。。。
子线程: 子任务4已准备就绪,等待其他子任务就绪。。。
子线程: 子任务6已准备就绪,等待其他子任务就绪。。。
子线程: 子任务7已准备就绪,等待其他子任务就绪。。。
子线程: 子任务5已准备就绪,等待其他子任务就绪。。。
子线程: 子任务10已准备就绪,等待其他子任务就绪。。。
子线程: 子任务8已准备就绪,等待其他子任务就绪。。。
子线程: 子任务9已准备就绪,等待其他子任务就绪。。。
主线程--> 所有子任务已准备就绪。。。
子线程: 子任务9开始执行
子线程: 子任务1开始执行
子线程: 子任务3开始执行
子线程: 子任务2开始执行
子线程: 子任务6开始执行
子线程: 子任务7开始执行
子线程: 子任务4开始执行
子线程: 子任务8开始执行
子线程: 子任务10开始执行
子线程: 子任务5开始执行
子线程: 子任务3执行完成
子线程: 子任务7执行完成
子线程: 子任务4执行完成
子线程: 子任务10执行完成
子线程: 子任务5执行完成
子线程: 子任务8执行完成
子线程: 子任务1执行完成
子线程: 子任务6执行完成
子线程: 子任务2执行完成
子线程: 子任务9执行完成
主线程--> 所有子任务已执行完成。。。
主线程--> 所有子任务执行完毕,获得结果:55</span>
ok,至此,就简单的介绍和如何使用CountDownLatch与CyclicBarrier。