CountDownLatch & CyclicBarrier源代码实现解析
CountDownLatch
CountDownLatch同意一条或者多条线程等待直至其他线程完毕以系列的操作的辅助同步器。
用一个指定的count值对CountDownLatch进行初始化。
await方法会堵塞,直至由于调用countDown方法把当前的count降为0,在这以后。全部的等待线程会被释放。而且在这以后的await调用将会马上返回。这是一个一次性行为——count不能被重置。假设你须要一个能够重置count的版本号。考虑使用CyclicBarrier。
事实上本类实现很easy。和ReentrantLock类似。公有的方法都是调用内部类的桥接模式,内部类是继承AQS的锁实现。详细例如以下:
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L; Sync(int count) {
setState(count);
} int getCount() {
return getState();
} protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
} protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
构造函数调用setState把count值设置为当前的状态。内部类Sync由CountDownLatch构造函数时创建,当然保证非负值也是这里推断,
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
另外。看看CountDownLatch的的await和countDown方法的实现:
public void countDown() {
sync.releaseShared(1);
} public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
能够看到await调了AQS的acquireSharedInterruptibly尝试获取共享锁,countDown方法调用了releaseShared尝试释放共享锁,两个方法的參数都是1。因此当此时有多条线程同一时候调用await时,这时候看到内部Sync类的tryAcquireShared方法实现,因为在构造函数里已经调用setState把当前锁状态数设置为count。因此这里在getState()的推断会一直返回count的值,因此tryAcquireShared会一直返回-1,然后这些调用await的线程都会进入等待队列。
继续看看countDown的实现。调用的是AQS的releaseShared的方法,经过调用来到内部Sync类的tryReleaseShared方法,明显看到方法仅仅是利用自旋把当前的锁状态数减去一。直到锁状态数等于0,然后返回true,这样就AQS就把先前进入等待队列里的全部等待共享锁的线程唤醒,同一时候假设后面继续有线程调用await的话,因为锁状态数已经变为0,因此tryAcquireShared会一直返回1,这时函数countDown就会立马返回,不须要再进入等待队列。
须要注意的是,这里的count值在构造函数就已经被决定了,兴许也没有方法能够改动,当然这是由这个类的最初设计意图所决定的。
假设须要能够对count值进行更改,能够參考CyclicBarrier。
CyclicBarrier
CyclicBarrier同意一组线程互相等待直到一个公平屏障点(common barrier point)。
与CountDownLatch不同的是CyclicBarrier着重与互相等待。而且加入重置原状态的方法。
在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时CyclicBarrier非常实用。
由于该屏障(barrier)在释放等待线程后能够重用,所以称它为循环的屏障。CyclicBarrier支持一个可选的Runnable命令,在一组线程中的最后一个线程到达之后(但在释放全部线程之前)。该命令仅仅在每一个屏障点执行一次。若在继续全部參与线程之前更新共享状态,此屏障操作非常实用。
假设屏障操作不依赖于线程组运行时被悬挂。则线程组内不论什么线程在被释放的时候都能够运行屏障操作。为了改进这个行为,每一个await的调用都会返回线程到达屏障的索引。然后你就能够选择哪条线程应该运行屏障操作。比如:
if (barrier.await() == 0) {
// 运行屏障操作
}
CyclicBarrier对于失败的同步尝试,会使用全有或者全无的破坏模型(breakage model):假设一条线程因为中断、异常或者超时提前离开了屏障点,其他全部在屏障点等待的线程也会通过抛出BrokenBarrierException(或者InterruptedException异常,同一时候被中断的情况下)离开屏障点。接下来看看详细的实现。
首先来看看CyclicBarrier类的构造函数和成员变量以及内部类Generation:
private static class Generation {
boolean broken = false;
}
CyclicBarrier声明一个内部类Generation,在每次屏障点的使用就代表着一个Generation实例。
当屏障点被破坏或者重置的时候。generation就要改变。
//保护屏障点入口的锁
private final ReentrantLock lock = new ReentrantLock();
//使线程在await中等待直至屏障点被脱落(tripped)的条件对象
private final Condition trip = lock.newCondition();
//须要调用await来脱落屏障点的线程数,为final变量
private final int parties;
//在屏障点被脱落之后须要执行的命令
private final Runnable barrierCommand;
//当前的Generation
private Generation generation = new Generation();
//在每次generation中从parties递减到0,当新的Generation被创建或者屏障点被破坏的时候,count就会被重置
private int count;
成员变量中採用lock和trip进行同步控制。另外parties和count记录线程数,generation则表示屏障点的当前状态,还有barrierCommand记录屏障点破坏后须要执行的命令。
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
当中parties表示在屏障点被脱落(tripped)之前须要调用await的线程数。须要注意的是parties是final变量,因此不能改变,而成员变量count则在每次await的时候递减,重置的时候把parties赋值就可以。barrierAction表示当屏障点被脱落的时候,运行的命令。要注意的时parties数在构造函数里设定以后就不能更改,假设屏障被脱落的时候,能够调用reset重置,我们先来看看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; 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();
}
} 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 {
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();
}
}
能够看到无超时版本号和超时版本号的await实现仅仅是在调用dowait函数的參数不同。详细来看看dowait的实现。和之前解析的ReentranLock,ReentrantReadWriteLock。以及上面的CountDownLatch不同,CyclicBarrier并没有重载AQS类,而是选择了直接使用ReentrantLock以及其Condition。
dowait函数一開始就调用RenntrantLock的lock方法尝试获取锁,当获取锁成功之后,然后创建栈变量g保留成员变量generation,因为generation在后面会被其他线程又一次赋值,因此用栈变量保留的做法在多线程同步是有用的技巧。
接着检验当前Generation的broken变量。假设该变量为true(在重置或者有足够线程调用类await破坏了屏障点)。则此刻立即抛出BrokenBarrierException异常,然后检查当前线程是否已经被中断,假设被中断则调用breakBarrier,依据CyclicBarrier的特性,当一个await的线程中断,则屏障点被破坏,则全部await的线程被唤醒抛出异常。
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
breakBarrier作用就是破坏屏障点,函数首先把成员变量的generation.broken变为true,重置count为parties值,然后调用条件对象trip的signalAll唤醒全部在await等待的线程。
在await以下我们即将会看到。breakBarrier会造成之前在await的全部线程抛出异常。
假设线程没有被中断,则把count自减,并保留至index(count相同会在后面释放锁的时候被其他线程改动)。假设index为0,则表示屏障点已经被破坏,然后假设barrierCommand非null,则运行命令,假设运行成功ranAction改动为true。否则在命令里抛出不论什么异常的话,则会在finally块调用breakBarrier破坏屏障点,唤醒其他线程。命令运行成功后,则会调用nextGeneration(脱落当前的屏障点):
private void nextGeneration() {
trip.signalAll();
count = parties;
generation = new Generation();
}
函数先唤醒全部在等待中的线程,然后重置count。接着创建一个新的Generation类实例,这样就等于把CyclicBarrier内部的状态重置。
我们再来看回dowait的实现,假设当前的index还没有递减到0,则会进入一个循环,在这里首先调用trip的await函数依照參数进行超时或无穷等待。假设在等待过程中,有线程抛出了InterruptedException中断了当前线程,则要继续推断generation是否和当前发生改变。假设栈变量g和generation相等,而且broken为false,则表示在await中发生了中断,因此要调用breakBarrier破坏屏障点,然后抛出异常;但假设以上条件不符合。则表明当前屏障点已经被脱落。但在线程仍在等待唤醒的过程中发生的中断,则此次中断应该于当前的await无关,则须要调用Thread.interrupt方法重置interrupt标识。
然后假设是被正常唤醒或者超时等待await以后。还要继续推断g.broken。假设为true,则表示屏障被破坏,要抛出BrokenBarrierException异常;假设栈变量g不和generation相等,则表示当前屏障点已经被脱落。因此要返回之前的index表达进入屏障点的索引。另外继续是否超时,假设超时则相同需要breakBarrier而且抛出TimeoutException。因为考虑到线程并发问题,假设以上推断都失败则必需要又一次循环。
最后离开函数的时候必需要调用lock.unlock释放锁。
另外,假设在await的线程数没有达到parties,但须要又一次同步,能够调用reset方法。
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
函数实现非常easy,尝试获取锁,然后调用breakBarrier和nextGeneration方法。这样调用之后,屏障点就会被破坏(breakage)。则把之前在await的线程唤醒并让它们抛出异常;然后调用nextGeneration重置当前状态,这样后来的await可以再次又一次等待。
总结
这样。我们就完整地把CountDownLatch和CyclicBarrier进行了分析。CountDownLatch着重于多组线程等待另外一组线程完毕操作。而且是无法重置的;CyclicBarrier则是着重于一组线程互相等待到对方都完毕操作为止,但能够重置。
假设要考虑到CountDownLatch为什么不提供一个可重置的方法。个人觉得考虑到实现重置,则必需要像CyclicBarrier一样要考虑到对其他正在等待线程的影响,这样势必就会使整个同步器模型更加复杂,令使用者不方便,同一时候也会加大实现难度。这样不如像JUC包给出的解决方式一样,提供CountDownLatch用于更普遍的简单的并发情况,另外再提供CyclicBarrier来为更加复杂的并发模型提供帮助。而其实CountDownLatch的同步模型比CyclicBarrier要简单。主要体如今等待线程之间不会互相影响,另外CountDownLatch的实现也要比CyclicBarrier更加简单。
CountDownLatch & CyclicBarrier源代码实现解析的更多相关文章
- Java并发包5--同步工具CountDownLatch、CyclicBarrier、Semaphore的实现原理解析
前言: JUC中提供了很多同步工具类,比如CountDownLatch.CyclicBarrier.Semaphore等,都可以作用同步手段来实现多线程之间的同步效果 一.CountDownLatch ...
- CountDownLatch、CyclicBarrier、Semaphore、Exchanger 的详细解析
本文主要介绍和对比我们常用的几种并发工具类,主要涉及 CountDownLatch . CyclicBarrier . Semaphore . Exchanger 相关的内容,如果对多线程相关内容不熟 ...
- CountDownLatch 和 CyclicBarrier 的运用及实现原理
I.CountDownLatch 和 CyclicBarrier 的运用 CountDownlatch: 定义: 其是一个线程同步的辅助工具,通过它可以做到使一条线程一直阻塞等待,直到其他线程完成其所 ...
- Java并发之CountDownLatch、CyclicBarrier和Semaphore
CountDownLatch 是能使一组线程等另一组线程都跑完了再继续跑:CyclicBarrier 能够使一组线程在一个时间点上达到同步,可以是一起开始执行全部任务或者一部分任务. CountDow ...
- Thread.join(), CountDownLatch、CyclicBarrier和 Semaphore区别,联系及应用
在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier和Semaphore,今天我们就来学习一下这三个辅助类的用法, 由于 ...
- JUC(3)---CountDownLatch、CyclicBarrier和AQS
CountDownLatch可以让一个线程等待其他线程完成了各自的工作之后再执行.比如说一个切菜,一个人切肉,都准备完毕之后才能炒肉. 构造方法: public CountDownLatch(int ...
- java多线程10:并发工具类CountDownLatch、CyclicBarrier和Semaphore
在JDK的并发包(java.util.concurrent下)中给开发者提供了几个非常有用的并发工具类,让用户不需要再去关心如何在并发场景下写出同时兼顾线程安全性与高效率的代码. 本文分别介绍Coun ...
- Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
Java并发编程:CountDownLatch.CyclicBarrier和Semaphore 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch ...
- 并发工具类:CountDownLatch、CyclicBarrier、Semaphore
在多线程的场景下,有些并发流程需要人为来控制,在JDK的并发包里提供了几个并发工具类:CountDownLatch.CyclicBarrier.Semaphore. 一.CountDownLatch ...
随机推荐
- Calling convention-调用约定
In computer science, a calling convention is an implementation-level (low-level) scheme for how subr ...
- 多任务-进程之Queue的进程间通信
1.经过线程和进程的对比,不难的知道,线程和进程有相当大的区别,如全局变量资源不能够共享. 2.在不同的进程间,如何实现通信呢? 需要提及的一个概念就是Queue,它是一个消息队列,下面通过一个例子来 ...
- BZOJ 4472 [Jsoi2015]salesman(树形DP)
4472: [Jsoi2015]salesman Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 417 Solved: 192[Submit][St ...
- Jtester使用
1.在Jtester中使用DataMap 为什么要使用DataMap? 早先的jTester中提供了dbFit方式来准备和验证数据库数据,应该来说,这个工具解决了很多问题.实际使用过程中,开发同学反映 ...
- Java多线程理解:线程安全的集合对象
1.概念介绍 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用.不会出现数据不一致或者数据污染. 线程不安全就 ...
- hpuoj--1695--一道签到题(KMP)
1695: 一道签到题 时间限制: 2 Sec 内存限制: 128 MB 提交: 72 解决: 36 [提交][状态][讨论版] 题目描述 我想说这是一道签到题,意思就是本次测试中最水的一道,不过 ...
- 无滚动条GridView少量图片展示
import android.content.Context; import android.util.AttributeSet; import android.util.Log; import an ...
- iview 分页的案例
//html部分 //js部分
- Data flow diagram-数据流图
A DFD shows what kind of information will be input to and output from the system, how the data will ...
- 文本域内容在div中带换行显示
function ReplaceSeperator(mobiles) { var i; var result = ""; var c; for (i = 0; i < mob ...