CyclicBarrier有点类似CountDownLatch,貌似都是个倒数计数器,这里暂且不去管异同点,先把CyclicBarrier分析完,应该就能知道不同了。
//标志是否当前代,当前代我是这么理解的,每一次对象的生成就属于一代(个人观点)
private static class Generation {
boolean broken = false;
}
/** The lock for guarding barrier entry */
//借助的依然是ReentrantLock锁,记得ReentrantLock内默认获取锁的方式是非公平锁
private final ReentrantLock lock = new ReentrantLock();
/** Condition to wait on until tripped */
//借助了Condition条件等待队列,还有一个是AQS内的SYS等待队列,两者需要区分,在实际使用中是结合起来的
//Condition队列中的线程是因为调用了await方法,进入了等待队列中,这时需要一个条件过来它才能加入到SYS队列中去竞争锁(注意这边)
//SYS队列里的线程呢是由于竞争锁失败而加入进去的
private final Condition trip = lock.newCondition();
/** The number of parties */
//总共有parties个线程需要进来
private final int parties;
/* The command to run when tripped */
//所有线程都进来之后执行的语句
private final Runnable barrierCommand;
/** The current generation */
//标志着哪一代,对于所有需要进入这一代的线程它们共享broken这个值,可以说成是区分每一代的屏障(屏障这个词来源于网络)
private Generation generation = new Generation();
//dowait是核心方法,会抛出3中异常,这3中异常都有各自的处理
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
//获取当前lock锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//当前代信息
final Generation g = generation;
if (g.broken)
//如果当前代被破坏了,直接抛出异常
//个人觉得对于某一代需要进入的线程来说的话,这一个判断应该是小概率事件
throw new BrokenBarrierException();
if (Thread.interrupted()) {
//当前进入的线程中断了,抛异常,也是小概率事件
breakBarrier(); //不过对于中断的线程来说的话,会执行breakBarrier操作
throw new InterruptedException();
}
//统计进入的线程数量
int index = --count;
if (index == 0) { // tripped
//全部进入时才能进入执行
boolean ranAction = false;
try {
//command是可以传进来的,当所有线程进入时,可以先执行
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
//执行成功了,唤醒下一代,这边唤醒下一代的意思呢?也就是执行外部线程的下面的逻辑段(这边是个人理解)
nextGeneration();
return 0;
} finally {
if (!ranAction)
//如果执行失败了,唤醒其他线程
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
//无限循环只有在没全进入时线程都需要进入
try {
if (!timed)
trip.await();//执行等待
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos); //设置超时时间执行等待
} catch (InterruptedException ie) {
//如果在执行await时发生InterruptedException异常,则抛出
if (g == generation && ! g.broken) {
//如果是当前代并且没有被破坏的话,将当前代标志设置成fasle,唤醒其他线程,最终抛出
breakBarrier();
throw ie;
} else {
//其他情况将线程标志为中断
//这里来看看着两个判断:
//g != generation,这种什么时候会出现呢?原本属于上一代的线程,然后又生成了新的generation对象,
//导致之前读取的g已经和generation不一致了,这种情况不会发生在第一个进入的线程,只能是后续线程不一致
//g.broken 被破坏,这个也很好理解,线程之间读取的值不一定就是最新的,有可能第一个线程进入到这边时,还是未被破坏的
//但是在这边刚刚要判断时,另外的线程破坏掉了。
//注意这边,可以看见处理breakBarrier()只能是第一个发现不一致的线程,那么后续线程不需要再做操作,直接抛出异常,也就是下面的逻辑段
Thread.currentThread().interrupt();
}
}
//对于await中断的线程,此处判断出被破坏了,直接抛出异常,不做处理
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生成,这就不难理解线程等待时有可能会出现代数不一致状态,因为有可能这个线程属于第一代,正准备中断时出现异常了,但是此时
//已经到了第二代了,这种情况应该小概率
generation = new Generation();
}
private void breakBarrier() {
//将当前代的标志置成true,标志被破坏了
generation.broken = true;
count = parties;
//唤醒在Condition队列中的其他线程,进入SYS队列中,获取锁
trip.signalAll();
}
测试个程序,来看看效果,把之前几种线程实现方式顺便写一写。
public class TestCycleB2 {
//注意此处设置的屏障是3个线程,如果只有2个进入的话,就会无限等待
private static CyclicBarrier cb = new CyclicBarrier(3);
public static void main(String[] args) {
//1.
MyThreadTest thread1 = new MyThreadTest("线程1",cb);
MyThreadTest thread2 = new MyThreadTest("线程2",cb);
MyThreadTest thread3 = new MyThreadTest("线程3",cb);
thread1.start();
thread2.start();
thread3.start();
//2.
for(int i = 0 ; i < 3; i++){
final MyClass my = new MyClass("t"+i,cb);
new Thread(new Runnable() {
@Override
public void run() {
my.run();
}
}).start();
}
//3.
for(int i = 0 ; i < 3; i++){
new Thread(new MyThreadTest2(new MyClass("t"+i,cb))).start();
}
}
}
class MyThreadTest extends Thread{
private CyclicBarrier cb;
public MyThreadTest(String name,CyclicBarrier cb){
super(name);
this.cb = cb;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ "即将进入等待");
try{
Thread.sleep(2000);
cb.await();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+ "继续执行");
}catch (Exception e){
e.printStackTrace();
}
}
}
class MyThreadTest2 implements Runnable{
private MyClass my;
public MyThreadTest2(MyClass my){
this.my = my;
}
@Override
public void run() {
my.run();
}
}
class MyClass{
private String name;
private CyclicBarrier cb;
public MyClass(String name,CyclicBarrier cb){
this.name = name;
this.cb=cb;
}
public void run(){
System.out.println(Thread.currentThread().getName()+ "即将进入等待");
try{
Thread.sleep(2000);
cb.await();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+ "继续执行");
}catch (Exception e){
e.printStackTrace();
}
}
}
得到的某一种情况,下面我们基于这种情况来分析,运行过程,加深理解。
Thread-0即将进入等待
Thread-2即将进入等待
Thread-1即将进入等待
Thread-0继续执行
Thread-2继续执行
Thread-1继续执行
基于上面某一次运行过程,可能存在下图运行图:
那我们来看看t0在调用await时发生了什么。主要就是释放当前线程占有的锁,加入到Condition队列中等待唤醒,并唤醒SYS队列中的线程,加入到获取锁的竞争中,此例中SYS没有其余线程。。Conditon队列的具体内部结构,不在这里分析了,前面文章已经分析过。
当t0释放出锁之后,由t2获取到锁,执行的逻辑与上图一致,最终也是中断在LockSupport处,并且加入到Condition中。至此大家全部park掉了,都等待。。。。注意,此处线程t1并没加入到Condition中,因为它是最后一个线程,进来之后就直接唤醒其他线程了,即发送signal信号.
当CyclicBarrier的count到达0时,就意味着大家都进来了,ok,就会执行trip.signalAll();这个signal操作就是之前分析的,与wait对应的唤醒操作,也就是Condition中线程锁等待的东东。
t1线程进入后发现全部到齐,那么就会执行唤醒操作了。唤醒操作中将Condition中的线程移到AQS等待队列中,直至t1释放锁之后,AQS等待队列中的线程被唤醒,去争夺锁。
被唤醒的t0线程,会去执行acquireQueue流程,这个在之前AQS文章中一分析过,后面的t2线程也是一样。上图对于AQS队列里的详情没有画的太精细,大致知道即可。
至此应该都分析的差不多了。。。。想到什么以后再补充吧。