上一篇我们主要通过ExecutorCompletionService与FutureTask类的源码,对Future模型体系的原理做了了解,本篇开始解读concurrent包中的工具类的源码。首先来看两个非常实用的工具类CyclicBarrier与CountDownLatch是如何实现的。


CyclicBarrier

CyclicBarrier直译过来是“循环屏障”,作用是可以使固定数量的线程都达到某个屏障点(调用await方发处)后,才继续向下执行。关于用法和实例本文就不做过多说明,现在直接进入CyclicBarrier的源码。

首先,来看下CyclicBarrier的几个标志性的成员变量:

 private static class Generation {
boolean broken = false;
}
/** The number of parties */
private final int parties;
/* The command to run when tripped */
private final Runnable barrierCommand;
/** The current generation */
private Generation generation = new Generation(); /**
* Number of parties still waiting. Counts down from parties to 0
* on each generation. It is reset to parties on each new
* generation or when broken.
*/
private int count;

这几个成员变量有以下说明:

  • 说明1:parties是final的,在构造时,传入的固定线程数,不可变;
  • 说明2:count是计数器,如果有线程到达了屏障点,count就减1;
  • 说明3:直到count=0时,其它线程才可以向下执行;
  • 说明4:barrierCommand是Runnable任务,在所有线程到达屏障点是,就执行barrierCommand,barrierCommand是构造时传入的,可以为空;
  • 说明5:generation比较复杂,是静态内部类Generation的实例,一个generation对象代表一代的屏障,就是说,如果generation对象不同,就代表进入了下一次的屏障,所以说,这个线程屏障是可循环的(Cyclic)。
  • 说明6:另外,generation的唯一的一个名为broken的成员变量代表屏障是否被破坏掉,破坏的原因可能是线程中断、失败或者超时等。如果被破坏,则所有线程都将抛出异常。

了解上述成员变量的说明后,基本上就可以知道了CyclicBarrier的实现原理,下面我们来看看代码是如何写的。其实实现很简单,我们只需通过await()方法就可以说明:

 public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen;
}
}

await()方法调用了真是的执行方法dowait(),这个方法里涵盖了所有乾坤:

 /**
* Main barrier code, covering the various policies.
*/
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation; if (g.broken)
throw new BrokenBarrierException(); if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
} int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
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) {
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();
}
}

代码第20行对应“说明2”。

代码第21行对应“说明3”。

代码第26行对应“说明4”。

代码第28行对应“说明5”,nextGeneration()方法中使用generation = new Generation();表示屏障已经换代,并唤醒所有线程。nextGeneration()请自行查看源码。

代码第16行第45行等所有调用breakBarrier()方法处,对应“说明6”,表示屏障被破坏,breakBarrier()方法中将generation.broken = true,唤醒所有线程,抛出异常。

最后,代码第40行处trip.await(),表示持有trip的线程进入等待被唤醒状态。

另外,实现中还有一个很重要的点,就是第8行的lock和第67行的unlock,保证同步状态下执行这段逻辑,也就保证了count与generation.broken的线程安全。

以上就是CyclicBarrier(循环使用的屏障)的源码实现,是不是比较简单。

CountDownLatch

CountDownLatch直译过来是“倒计数锁”。在线程的countDown()动作将计数减至0时,所有的await()处的线程将可以继续向下执行。CountDownLatch的功能与CyclicBarrier有一点点像,但实现方式却很不同,下面直接来观察CountDownLatch的两个最重要的方法:

 public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
} public void countDown() {
sync.releaseShared(1);
}

可以看到,这两个方法实际是由静态内部类Sync来实现的。这个Sync我们在上一篇FutureTask的实现中也见过,那我们就先简单介绍下Sync究竟是用来做什么的:

Sync extends AbstractQueuedSynchronizer

这个抽象类AbstractQueuedSynchronizer是一个框架,这个框架使用了“共享”与“独占”两张方式通过一个int值来表示状态的同步器。类中含有一个先进先出的队列用来存储等待的线程。

这个类定义了对int值的原子操作的方法,并强制子类定义int的那种状态是获取,哪种状态是释放。子类可以选择“共享”和“独占”的一种或两种来实现。

共享方式的实现方式是死循环尝试获取对象状态,类似自旋锁。

独占方式的实现方式是通过实现Condition功能的内部的类,保证独占锁。

而我们正在解读的CountDownLatch中的内部类Sync是使用的共享方式,对于AbstractQueuedSynchronizer的解读本篇不打算详细说明,因为笔者对“独占”方式还没彻底弄通,如果以后有机会再来补充。

接下来就直接观察CountDownLatch.Sync的源码:

 /**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; Sync(int count) {
setState(count);
} int getCount() {
return getState();
} public int tryAcquireShared(int acquires) {
return getState() == 0? 1 : -1;
} public boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}

结合最初列出的await()和countDown()方法,

通过上述代码第9行可以看到,CountDownLatch将构造时传入的用来倒计数的count作为状态值。

通过上述代码第17行可以看到,CountDownLatch定义了当count=0时表示可以共享获取状态(在await()方法中调用的sync.acquireSharedInterruptibly(1)会死循环尝试获取状态)。

通过上述代码第26行可以看到,CountDownLatch定义了当count-1表示一次共享释放状态(在countDown()方法中调用的sync.releaseShared(1)会涉及)。

以上就是CountDownLatch的源码实现。

总结

CyclicBarrier与CountDownLatch有一点相似之处,但是有很大区别。它们的异同我个人总结如下:

类似功能

  • CyclicBarrier与CountDownLatch都是通过计数到达一定标准后,使得在await()处的线程继续向下执行。

不同之处

  • CyclicBarrier的实现是通过线程的等待唤醒;CountDownLatch的实现是通过死循环访问状态的自旋机制
  • CyclicBarrier在线程改变计数后不能向下执行(await()改变计数);CountDownLatch在线程改变计数后继续向下执行(countDown()改变计数)
  • CyclicBarrier的计数可以被重置,循环使用;CountDownLatch的计数只能使用一次

JDK源码分析之concurrent包(四) -- CyclicBarrier与CountDownLatch的更多相关文章

  1. JDK源码分析之concurrent包(一) -- Executor架构

    Java5新出的concurrent包中的API,是一些并发编程中实用的的工具类.在高并发场景下的使用非常广泛.笔者在这做了一个针对concurrent包中部分常用类的源码分析系列.本系列针对的读者是 ...

  2. JDK源码分析之concurrent包(三) -- Future方式的实现

    上一篇我们基于JDK的源码对线程池ThreadPoolExecutor的实现做了分析,本篇来对Executor框架中另一种典型用法Future方式做源码解读.我们知道Future方式实现了带有返回值的 ...

  3. JDK源码分析之concurrent包(二) -- 线程池ThreadPoolExecutor

    上一篇我们简单描述了Executor框架的结构,本篇正式开始并发包中部分源码的解读. 我们知道,目前主流的商用虚拟机在线程的实现上可能会有所差别.但不管如何实现,在开启和关闭线程时一定会耗费很多CPU ...

  4. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  5. 【JDK】JDK源码分析-TreeMap(2)

    前文「JDK源码分析-TreeMap(1)」分析了 TreeMap 的一些方法,本文分析其中的增删方法.这也是红黑树插入和删除节点的操作,由于相对复杂,因此单独进行分析. 插入操作 该操作其实就是红黑 ...

  6. 【JDK】JDK源码分析-Vector

    概述 上文「JDK源码分析-ArrayList」主要分析了 ArrayList 的实现原理.本文分析 List 接口的另一个实现类:Vector. Vector 的内部实现与 ArrayList 类似 ...

  7. 【JDK】JDK源码分析-AbstractQueuedSynchronizer(2)

    概述 前文「JDK源码分析-AbstractQueuedSynchronizer(1)」初步分析了 AQS,其中提到了 Node 节点的「独占模式」和「共享模式」,其实 AQS 也主要是围绕对这两种模 ...

  8. 手机自动化测试:appium源码分析之bootstrap十四

    手机自动化测试:appium源码分析之bootstrap十四   poptest(www.poptest.cn)是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开 ...

  9. JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable

    JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...

随机推荐

  1. 【HTML5 WebSocket】WebSocket对象特性和方法

    <HTML5 WebSocket权威指南>学习笔记&3 WebSocket方法的对象特性 1. WebSocket方法 a. send方法 send方法用于在WebSocket连接 ...

  2. XML - 十分钟了解XML结构以及DOM和SAX解析方式

    引言 NOKIA 有句著名的广告语:"科技以人为本".不论什么技术都是为了满足人的生产生活须要而产生的.详细到小小的一个手机.里面蕴含的技术也是浩如烟海.是几千年来人类科技的结晶, ...

  3. git 这一篇就够80%使用情况了

    转 Git 命令详解及常用命令整理 Git 命令详解及常用命令 Git作为常用的版本控制工具,多了解一些命令,将能省去很多时间,下面这张图是比较好的一张,贴出了看一下: 关于git,首先需要了解几个名 ...

  4. SpringCloud系列十六:Feign使用Hystrix

    1. 回顾 上文讲解了使用注解@HystrixCommand的fallbackMethod属性实现回退.然而,Feign是以接口形式工作的, 它没有方法体,前文讲解的方式显然不适用与Feign. 事实 ...

  5. [na][win]系统优化工具dism++

    系统优化工具, 确实能将c盘扩大个2-3g. 主要是删除日志 优化系统等功能. https://www.chuyu.me/

  6. zookeeper伪分布式安装

    本文介绍zookeeper伪分布式安装. 所谓 “伪分布式集群” 就是在1台PC中启动多个zookeeper的实例.“完全分布式集群” 是每1台PC启动1个ZooKeeper实例. 由于我的测试环境P ...

  7. Macbook小问题

    Macbook小问题 有时候 AppStore 和 Safari,QQ等 无法上网,但 chrome 却是正常的.解决办法:终端输入如下命令,其实是在 kill 掉网卡进程. sudo killall ...

  8. 说说Java中的资源文件的读取

    最近在看spring的资源获取时发现JDK里存在几种不同方式的资源获取,因比较混乱特地总结起来帮助和我一样混乱的人理解.下面是我项目的类结构图,在 src/main/java 下有两个类 Resour ...

  9. 去掉IntelliJ IDEA代码编辑区域的竖线

    (网络配图) 作为从事编程或者测试工作的人来说,尤其是有强迫症的,看着非常痛苦,我们来看看怎么去掉 在 Settings-> Editor-> General-> Appearanc ...

  10. ERROR C2676

    直接上代码: nl.h #ifndef NL_H #define NL_H #include <iosfwd> namespace ZJ { /** Insert a newline ch ...