扫描下方二维码或者微信搜索公众号
菜鸟飞呀飞
,即可关注微信公众号,阅读更多Spring源码分析
和Java并发编程
文章。
上一篇文章介绍了工具类
CountDownLatch
的原理和使用场景(并发工具类CountDownLatch的源码分析以及使用场景),今天将介绍JUC包下另一个十分常用的并发工具类CyclicBarrier
,翻译过来就是可循环
使用的屏障
。
控制线程的执行顺序
,但是它与CountDownLatch的区别是,CyclicBarrier是让一组线程阻塞在同一屏障(同步点)处,直到最后一个线程到达屏障(也就是屏障的计数器减为0),屏障才会打开,这些阻塞在屏障处的线程才会继续往下执行。CountDownLatch是让一组或者一个线程等待其他线程执行完后,当前线程才继续执行。另外一点区别就是,CyclicBarrier的计数器减为0后,可以重置计数器,从而可以再次使用,这一点通过类名中含有Cyclic
(循环)就能看出。而CountDownLatch的计数器减为0后,不会重置,因此不能重复使用。int类型
的参数,用来指定屏障的大小
(即当多少个线程到达屏障后,屏障打开),然后在线程中调用await()
方法即可让线程阻塞在屏障处。public class CyclicBarrierDemo {
public static void main(String[] args) {
Random random = new Random();
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
List<Thread> threads = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
threads.add(new Thread(()->{
int time = random.nextInt(5) + 1;
try {
// 通过线程休眠来模拟每位运动员的准备时间
Thread.sleep(time * 1000);
System.out.println(Thread.currentThread().getName() + "准备就绪");
// 运动员准备就绪后,就示意发令员自己准备好了,即调用await()方法
cyclicBarrier.await();
System.out.println("起跑枪响,"+Thread.currentThread().getName() + "起跑");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},"运动员"+(i+1)));
}
for (Thread thread : threads) {
thread.start();
}
}
}
CyclicBarrier
和CoutDownLatch
的底层实现也存在一点区别,CountDownLatch底层是直接
通过组合一个继承了AQS的同步组件来实现的,而CyclicBarrier并没有直接
借助AQS的同步组件,而是通过组合ReentrantLock这把锁
来实现的(ReentrantLock的底层实现依然使用的AQS来实现的,归根结底,CyclicBarrier的底层实现也是AQS)。lock
。在CyclicBarrier中还维护了一个计数器:count
。由于CyclicBarrier可以重复使用,即计数器减为0后,将其重置,因此还需要借助另外一个变量来存放count的初始值,这个变量就是parties
。CyclicBarrier中有个属性是generation,其类型是一个CyclicBarrier的内部类Generation,它的作用是用来实现await(long timeout,TimeUnit unit)
方法的超时等待的功能(后面分析源码时会详细解释)。当CyclicBarrier重置时,也会重新令generation重置赋值。CyclicBarrier的属性和方法见下表。属性或者方法 | 作用 |
---|---|
ReentrantLock lock | 用来保证线程安全,防止多个线程同时修改count时,出现线程不安全的情况 |
int count | 计数器,当调用await()方法时,会令count减1 |
int parties | 记录计数器的初始值 |
Generation generation | 当计数器重置时,也会重置该属性。当出现超时等待时,会令generation中的broken属性为true。 |
Condition trip | 等待队列 |
Runnable barrierCommand | CyclicBarrier支持当计数器减为0后,先执行一个Runnable任务,然后执行阻塞在屏障处的线程 |
await() | 让线程等待在阻塞在屏障处,并令计数器减1,不支持超时等待 |
await(long timeout, TimeUnit unit) | 让线程等待在阻塞在屏障处,最大等待timeout的单位时间,并令计数器减1 |
reset() | 重置屏障 |
// parties用来指定计数器的大小
// barrierAction是一个Runnable,当计数器减为0时,会先执行barrierAction,然后再打开屏障
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
// parties用来指定计数器的大小
public CyclicBarrier(int parties) {
// 调用有两个参数的有参构造方法
this(parties, null);
}
CyclicBarrier cyclicBarrier = new CyclicBarrier(10);
这一行代码时,会初始化计数器count的值和parties。传入的参数10,表示当有10个线程到达屏障时,才会打开屏障。dowait()
方法。dowait()方法的源码如下。private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
// 如果有线程调用了await(long timeout,TimeUnit unit)方法,且出现了超时等待,那么此时g.broken就为true,因此会抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 如果线程被中断,那么就直接中断屏障(让所有等待的线程醒来)
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 计数器递减
int index = --count;
// 如果递减后的结果为0,说明所有线程达到屏障
if (index == 0) {
// tripped
boolean ranAction = false;
try {
// 判断有没有需要优先执行的任务,有就执行
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 在nextGeneration()会唤醒等待队列中的所有线程,边让计数器的count值重置
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
// 如果计数器没有减到0,就让当前线程进入到等待队列中等待
for (;;) {
try {
// timed是用来标识是否是超时等待
if (!timed)
// 调用condition的await()方法,进入到等待队列
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();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
唤醒等待队列中的所有线程,并重置计数器
。其源码如下。private void nextGeneration() {
// signal completion of last generation
// 唤醒等待队列中所有在等待的线程
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
没有到达屏障,还不能打开屏障
,因此就需要令当前线程加入到等待队列中,即会调用trip.await()
,让线程等待。catch语句块
中,在catch语句块中调用了breakBarrier()方法,breakBarrier()方法的主要作用就是将generation的broken属性设置true
。那么当执到if(g.broken)
就会判断成立,然后抛出异常,这样就实现了超时等待功能。private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
trip.await()
方法处醒来,继续执行后面的逻辑。关于ReentrantLock的详细分析可以参考这两篇文章:可重入锁(ReentrantLock)源码分析 和
公平锁与非公平锁的对比public class CyclicBarrierDemo {
public static void main(String[] args) {
Random random = new Random();
// 在CyclicBarrier构造方法中,第二个参数传入一个Runnable。
CyclicBarrier cyclicBarrier = new CyclicBarrier(10, new Runnable() {
@Override
public void run() {
System.out.println("============== 各就位!!!预备!!!砰!============");
}
});
List<Thread> threads = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
threads.add(new Thread(()->{
int time = random.nextInt(5) + 1;
try {
Thread.sleep(time * 1000);
System.out.println(Thread.currentThread().getName() + "准备就绪");
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "起跑");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},"运动员"+(i+1)));
}
for (Thread thread : threads) {
thread.start();
}
}
}
============== 各就位!!!预备!!!砰!============
这一行后,才会让其他线程继续执行。