1.CyclicBarrier简单介绍
1.1运行示意图
1.2简单测试
import java.util.Random;
import java.util.concurrent.*;
/**
* 使用线程池启动5个任务,模拟王者荣耀英雄进入游戏的过程。当所有的线程全部加载完毕后才能
* 进入游戏,首先创建一个CyclicBarrier,初始化state = 5,每个线程加载完毕后调用await()
* 方法阻塞,当所有的线程全部加载完毕阻塞后,state = 0,这时将所有的线程都唤醒。
*/
public class CyclicBarrierTest {
public static void main(String[] args) {
String[] heros = new String[]{
"安琪拉", "亚瑟", "马超", "张飞", "刘备"};
ExecutorService threadPool = Executors.newFixedThreadPool(5);
CyclicBarrier barrier = new CyclicBarrier(5);
//开启5个线程
for (int i = 0; i < 5; i++) {
threadPool.execute(new Player(heros[i], barrier));
}
threadPool.shutdown();
}
static class Player implements Runnable {
private String hero;
private CyclicBarrier barrier;
Player(String hero, CyclicBarrier barrier) {
this.hero = hero;
this.barrier = barrier;
}
@Override
public void run() {
try {
//模拟等待时间
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
System.out.println(hero + ": 加载进度100%, 等待其他玩家加载完成中。。。");
//加载完毕 await()阻塞,等待其他线程加载完毕
barrier.await();
System.out.println(hero + "发现所有英雄加载完成,开始战斗吧 !");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
/* 运行结果
刘备: 加载进度100%, 等待其他玩家加载完成中。。。
亚瑟: 加载进度100%, 等待其他玩家加载完成中。。。
安琪拉: 加载进度100%, 等待其他玩家加载完成中。。。
张飞: 加载进度100%, 等待其他玩家加载完成中。。。
马超: 加载进度100%, 等待其他玩家加载完成中。。。
马超发现所有英雄加载完成,开始战斗吧 !
刘备发现所有英雄加载完成,开始战斗吧 !
安琪拉发现所有英雄加载完成,开始战斗吧 !
亚瑟发现所有英雄加载完成,开始战斗吧 !
张飞发现所有英雄加载完成,开始战斗吧 !
*/
2.源码解析
2.1内部结构分析
public class CyclicBarrier {
//表示 "代" 这个概念
private static class Generation {
/*
* 表示当前代是够被打破,如果代被打破,那么再来到这一代的线程,就会直接抛出
* BrokenException异常,且在这一代挂起的线程都会被唤醒,然后抛出异常BrokenException。
*
* 在await()方法时,正常情况下被阻塞的线程被唤醒后,如果跳出await()就会判断
* 原代和新代是否是一个,因为最后一个达到的线程会将创建新代。
*/
boolean broken = false;
}
//因为CyclicBarrier是依赖于Condition等待队列的,而Condition等待队列必须依赖lock
private final ReentrantLock lock = new ReentrantLock();
/*
* 线程挂起实现使用的等待队列,条件:当前代所有线程到位(count = 0),这个等待队列的线程才会被唤醒
*/
private final Condition trip = lock.newCondition();
//barrier需要参与进来的线程数量。
private final int parties;
//当前代 最后一个到位的线程需要执行的事件
private final Runnable barrierCommand;
//barrier对象,当前代
private Generation generation = new Generation();
//当前代还有多少个线程未到位。 (初始值为parties)。
private int count;
/*
* 构造器
* @param parties 表示需要参与的线程数量,每次屏障需要参与的线程数
* @param barrierAction 当前 "代" 最后一个到位的线程,需要执行的事件(可以为NULL)
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
//parties <= 0 抛出异常
if (parties <= 0) throw new IllegalArgumentException();
//为内部属性赋值
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
2.2简单方法分析
/*
* 开启下一代,当这一代 所有线程到位后(假设barrierCommond不为空,还需要最后一个线程
* 执行玩事件), 会调用nextGeneration()开启下一代。
*/
private void nextGeneration() {
//将在条件队列内挂起的线程 全部唤醒
trip.signalAll();
//重置count
count = parties;
//开启下一代 使用一个新的generation对象 表示新的一代,新的一代和上一代没有任何关系
generation = new Generation();
}
/*
* 打破barrier屏障,在屏障内部的线程 都会抛出异常
*/
private void breakBarrier() {
/*
* 将 "代"中的broken设置为true,表示这一代是被打破了的,再来到这一代的线程
* 直接抛出异常
*/
generation.broken = true;
//重置count
count = parties;
/*
* 将等待队列中的线程全部唤醒,唤醒后的线程会检查当前代是否是被打破的,
* 如果是被打破的话,接下来的逻辑和开启下一代唤醒的逻辑不一样。
*/
trip.signalAll();
}
2.3await()方法
public int await() throws InterruptedException, BrokenBarrierException {
try {
//底层调用的是dowait()方法,这里分析一个不带超时时间的,dowait()
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe);
}
}
// ||
// ||
// ||
// \/
/*
* @param timed 表示当前调用await()方法的线程是否指定了超时时长,
* @param nanos 表示线程等待超时时长,如果timed = false,那么nanos = 0.
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
//获取全局锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取barrier当前的 "代"
final Generation g = generation;
//如果当前代已经被打破状态,则当前调用await()方法的线程,直接抛出Broken异常
if (g.broken)
throw new BrokenBarrierException();
//如果当前线程的中断标志位为true,则打破当前代,然后当前线程抛出中断异常。
if (Thread.interrupted()) {
/*
* 此方法将代中的broken设置为true,并重置count,唤醒等待队列的所有节点
*/
breakBarrier();
//抛出中断异常。
throw new InterruptedException();
}
/*
* 线程执行到这里,说明当前线程中断状态是正常的(false),并且当前
* "代"的broken为false(未打破状态)
*/
// 将count - 1 赋值给 index
int index = --count;
/*
* 条件成立:表示当前线程是最后一个到达barrier的线程。
*/
if (index == 0) {
// tripped
/*
* ranAction -> true 表示最后一个到达barrier的线程在执行
* 内部的barrierCommand任务时没有抛出异常,否则抛出了异常
*/
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
//barrierCommand不为NULL,将其执行
if (command != null)
command.run();
//设置标记位为true。
ranAction = true;
/*
* 开启新一代
* 1.唤醒等待队列内的线程,被唤醒的线程会依次获取到锁(state),然后依次退出await方法
* 2.重置count
* 3.创建一个新的generation,表示新的一代
*/
nextGeneration();
//返回0,因为当前线程是此代最后一个到达的线程,所以index == 0
return 0;
} finally {
//执行barrierCommand出现异常
if (!ranAction)
//打破屏障
breakBarrier();
}
}
/*
* 执行到这里,说明当前线程并不是最后一个到达barrier的线程,此时需要进入自旋。
*/
//自旋 一直到条件满足或者当前代被打破、线程被中断、等待超时
for (;;) {
try {
//条件成立: 说明当前线程是不指定超时时间的
if (!timed)
//当前线程会释放掉lock,然后进入等待队列的尾部,然后挂起等待被唤醒
trip.await();
//响应超时时间
else if (nanos > 0L)
//调用awaitNanos方法。(带超时的阻塞)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
/*
* 抛出中断异常,会进来这里
* 什么时候会抛出中断异常呢?
* Node节点在等待队列内收到中断信号,会抛出中断异常
*/
/*
* g == generation 表示当前代并没有变化
* !g.broken 当前代如果没有被打破,那么当前线程就去打破,并
* 且抛出异常
*/
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
/*
* 执行到else有几种情况?
* 1.代发生了变化,这个时候就不需要抛出中断异常了,因为代已经更
* 新了,这里唤醒后就走正常逻辑了,只不过设置下中断标记
* 2.代没有发生变化,但是代被打破了,此时也不用返回中断异常,
* 执行到下面的时候会抛出 brokenBarrier异常,也记录下中断标志位
*/
Thread.currentThread().interrupt();
}
}
/*
* 唤醒后执行到这里? 有几种情况?
* 1.正常情况,当前barrier开启了新的一代
* 2.当前generation被打破,此时也会唤醒所有在trip上挂起的线程
* 3.当前线程在等待队列中超时,然后主动转移到同步队列,然后获取到锁
*/
// 表示当前代已经被打破,
if (g.broken)
//线程唤醒后依次抛出BrokenBarrier异常
throw new BrokenBarrierException();
//条件成立:说明当前线程挂起期间,最后一个线程到位了,然后触发了开启新一轮的逻辑,此时唤醒等待队列中的线程,
//这是一次正常的线程被唤醒后退出的逻辑
if (g != generation)
return index;
//超时判断。
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
//解锁。
lock.unlock();
}
}