1. 简介

JUC中的CyclicBarrier提供了一种多线程间的同步机制,可以让多个线程在barrier等待其它线程到达barrier。正如其名CyclicBarrier含义就是可以循环使用的屏障。

2. 源码解读

2.1 数据结构

2.1.1 Generation

在CyclicBarrier中用Generation来代表每一轮的Cyclibarrier的运行状况。

private static class Generation {
// broken表示挂否。
boolean broken = false;
}
private Generation generation = new Generation();

在任意时刻只有一个genration实例是真正代表当前这一轮的运行状况,其他实例都是跑完或者跑挂的。

2.1.2 barrierCommand

CyclicBarrier允许我们通过构造方法设置一个Runnable对象,用来在所有线程都到达barrier时执行。

2.1.3 其它

parties表示线程数,在parties个线程都调用await方法后,barrier才算是被通过(tripped)了。

count表示还剩下未到达barrier(未调用await)的线程数量,count会在新的一轮开启或者当前这一轮跑挂时重置为parties。

CyclicBarrier中的trip用于实现线程间的等待与唤醒的通信,而lock则为CyclicBarrier中的变量(generation和count)提供可见性保证,为临界区的操作提供保护。

private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition(); private final int parties;
private int count;

2.2 await方法

下面分析await方法的源码。

public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
} public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
} private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation; // 如果已经跑挂了,抛出BrokenBarrierException。
if (g.broken)
throw new BrokenBarrierException(); // 检查中断标志位。
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
} int index = --count;
// 最后一个到达barrier的线程。
if (index == 0) {
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 开启下一轮。
nextGeneration();
return 0;
} finally {
// 如果action执行时发生了,也会break掉barrier。
if (!ranAction)
breakBarrier();
}
} // loop until tripped, broken, interrupted, or timed out
/*
* 对于其它(不是最后一个)线程,会在trip条件下等待被唤醒。情况有以下几类:
* 1. 所有线程都到达barrier,并成功执行了barrierAction。
* 2. 有线程执行了breakBarrier方法。
* 3. 线程本身被中断。
* 4. 超时(如果调用的带时间限制的await)。
*/
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
/*
* g == generation && !g.broken说明此时当前这一轮还没结束,并且没有其它线程执行过breakBarrier方法。
* 这种情况会执行breakBarrier置generation的broken标识为true并唤醒其它线程,之后继续抛出InterruptedException。
*/
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
/*
* 如果g != generation,此时这一轮已经结束,后面返回index作为到达barrier的次序;
* 如果g.broken说明之前已经有其它线程执行了breakBarrier方法,后面会抛出BrokenBarrierException。
*/
Thread.currentThread().interrupt();
} } if (g.broken)
throw new BrokenBarrierException(); // 这一轮已经结束,则返回到达屏障的次序,0表示最后一个,parties-1表示第一个。
if (g != generation)
return index; // 判断是否超时。
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
} // 保证调用时持有锁。
private void nextGeneration() {
// 唤醒其它在trip条件下等待的线程。
trip.signalAll();
// 重置count。
count = parties;
// 开启下一轮。
generation = new Generation();
} // 保证调用时持有锁。
private void breakBarrier() {
generation.broken = true;
// 重置count。
count = parties;
// 唤醒其它在trip条件下等待的线程。
trip.signalAll();
},

2.3 其它方法

CyclicBarrier其它还提供了例如getParties, isBroken, getNumberWaiting, reset等方法,都比较简单。

其中除了getParties由于parties被final修饰不可变,其余方法都会先去获得互斥锁。


/**
* 获取当前这一轮是否已经broken。
*/
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
} /**
* 重置barrier到初始状态,所有还在等待中的线程最终会抛出BrokenBarrierException。
*/
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中等待的线程数。
*/
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count;
} finally {
lock.unlock();
}
}

3. 总结

总的来说CyclicBarrier的源码还是比较简洁易懂的,通过锁和条件,实现了在barrier上同步的功能。

常常会拿CyclicBarrier和CountdownLatch比较,CountdownLatch的的计数器到0就完事了,没法再重置恢复。而CyclicBarrier的计数器可以通过正常的一轮同步重置,也可以通过reset方法强制重置。CountdownLatch每个调用await的线程会被阻塞直到其它线程通过countDown方法将计数器减到0;而CyclicBarrier则是有parties-1个线程调用await会阻塞直到最后一个线程调用await方法。此外CyclicBarrier还可以设置一个barrierAction,相当于一个hook,这也是CountdownLatch不具有的。

CyclicBarrier源码解读的更多相关文章

  1. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  2. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  3. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  4. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  5. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  6. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

  7. SDWebImage源码解读之SDWebImageCache(下)

    第六篇 前言 我们在SDWebImageCache(上)中了解了这个缓存类大概的功能是什么?那么接下来就要看看这些功能是如何实现的? 再次强调,不管是图片的缓存还是其他各种不同形式的缓存,在原理上都极 ...

  8. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  9. AFNetworking 3.0 源码解读 总结(干货)(上)

    养成记笔记的习惯,对于一个软件工程师来说,我觉得很重要.记得在知乎上看到过一个问题,说是人类最大的缺点是什么?我个人觉得记忆算是一个缺点.它就像时间一样,会自己消散. 前言 终于写完了 AFNetwo ...

随机推荐

  1. SpringMVC之文件上传异常处理

    一般情况下,对上传的文件会进行大小的限制.如果超过指定大小时会抛出异常,一般会对异常进行捕获并友好的显示出来.以下用SpringMVC之文件上传进行完善. 首先配置CommonsMultipartRe ...

  2. 升级mac Mojave系统,git无法使用

    升级mac Mojave系统后 无法使用git,出现如下问题 xcrun: error: invalid active developer path (/Library/Developer/Comma ...

  3. python装饰器带括号和不带括号的语法和用法

    装饰器的写法补充: 通常装饰器的写法是@func(),而有的时候为了减少出错率,可能会写成@func,没有()括号,这时我们可以这样定义,来减少括号.下面通过两个例子还看. 一般装饰器的写法: def ...

  4. 小白Python路上第一个难点,也是一个比较重要的点(闭包,迭代器,生成器)

    一.闭包 闭包就是在内层函数中引用外层函数的变量 作用:1.保护变量不受侵害          2.让一个变量永驻内存 二.迭代器 Iterator:迭代器,包含_iter_()和_next_()函数 ...

  5. Extjs 项目中常用的小技巧,也许你用得着(2)

    接着来,也是刚刚遇到的 panel怎么进行收缩 collapsible: true, 这会panel就会出现这个 点这个就可以收缩了 panel怎么随便拉伸,也就是让那个小黑三角出现 split: t ...

  6. Bootstrap学习记录-3.Badge、Breadcrumb、Buttons、 Button Group、Card、Carousel

    1. Badge Badge作为数值标记组件,它能作为链接或按钮的一部分来提供计数作用,而且它通过使用相对字体大小来适应父级元素的大小.它的最基本的修饰符为.badge .badge-*. <b ...

  7. 做webApp遇到的一些坑及解决方案

    1.问题:手机端click事件会有大约300ms的延迟 原因:手机端事件touchstart-->touchmove-->touchend or touchcancel-->clic ...

  8. Python全栈学习_day005知识点

    今日内容大纲: . 字典的增删改查以及其他操作 . 字典的嵌套 . 字典的增删改查以及其他操作 , 'sex': '男'}, 'name_list': ['无双', 'alex', 'BlameK'] ...

  9. deepin使用笔记-解决安装并解决gvim没有启动器的问题

    我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! 1.安装gvim #apt-get install vim-gtk3 2.创建桌面启动器 创建/usr/share/applic ...

  10. 从零开始学习html(六)开始学习CSS,为网页添加样式

    一.认识CSS样式 <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type&quo ...