Java 并发 --- CyclicBarrier源码分析
来源:互联网 发布:windows扩展屏 编辑:程序博客网 时间:2024/05/21 09:53
CyclicBarrier 介绍(jdk 1.8)
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier),它的功能是让一组线程到达一个屏障(也就叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
在前面,我们分析过CountDownLatch,CountDownLatch 主要用于一个线程等待其它线程都完成后,再继续执行,而CyclicBarrier 更多的则是屏障作用,在同一个地方,等待其它线程全部执行到这里,然后再放行,当然CyclicBarrier 也完全可以实现CountDownLatch 的功能。CyclicBarrier 还有一个比较特殊的功能:就是当所有线程到达屏障后,可以执行某个任务。
CountDownLatch 的计数器只能使用一次,而CyclicBarrier的计数器可以重置,得以重新利用。
CountDownLatch 内部使用的队列同步器(AQS),CyclicBarrier 中使用的ReentrantLock 来实现同步,而ReentrantLock 是基于AQS 是实现的,因此其它它们的底层实现都是一样的。
在CountDownLatch 对AQS 做了一个简单的回顾,可以参过下面内容:
Java 并发 — CountDownLatch源码分析
更多的可以参考下面内容:
Java 并发 —ReentrantLock源码分析
Java 并发 —AbstractQueuedSynchronizer(同步器)-独占模式
Java 并发 —AbstractQueuedSynchronizer-共享模式与Condition
使用
class MyThread extends Thread { private CyclicBarrier cb; public MyThread(String name, CyclicBarrier cb) { super(name); this.cb = cb; } public void run() { System.out.println(Thread.currentThread().getName() + " going to await"); try { cb.await(); System.out.println(Thread.currentThread().getName() + " continue"); } catch (Exception e) { e.printStackTrace(); } }}//最后一个线程到屏障后 执行的任务class Task implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName() + "execute the barrierCommand"); }}public class CyclicBarrierDemo { public static void main(String[] args) throws InterruptedException, BrokenBarrierException { CyclicBarrier cb = new CyclicBarrier(2, new Task()); MyThread thread1 = new MyThread("thread1", cb); MyThread thread2 = new MyThread("thread2", cb); thread1.start(); thread2.start(); thread1.join(); thread2.join(); }}
当线程执行到屏障时(cb.await()),如果不是最后一个到来的线程,则进行等待,如果是最后一个到来的线程,则执行指定的任务(可选),然后唤醒所有阻塞在该屏障的线程,继续往下执行。
数据结构
// 可重入锁private final ReentrantLock lock = new ReentrantLock();// 条件队列private final Condition trip = lock.newCondition();// 参与的线程数量private final int parties;// 由最后一个进入 barrier 的线程执行的操作,也就是所有线程到达屏障后执行的任务private final Runnable barrierCommand;// 表示当前的屏障(第x代)private Generation generation = new Generation();// 正在等待进入屏障的线程数量,初始值是等于parties值的private int count;//屏障控制private static class Generation { boolean broken = false; //表示屏障是否被破坏}
CyclicBarrier 使用一个可重入锁来进行同步操作,通过Condition来进行条件阻塞操作。
Generation 指的是当前的屏障,因为当所有线程到达该屏障时,该屏障完成任务,同时屏障可以被重设,再次利用,当再次利用时,则会重新生成Generation 来指代当前屏障,这个可以结合后面代码来分析。
构造方法
1、指定参与的线程数量
public CyclicBarrier(int parties) { this(parties, null);}
2、指定参与的线程数量和所有线程到底屏障后执行的任务
public CyclicBarrier(int parties, Runnable barrierAction) { if (parties <= 0) throw new IllegalArgumentException(); this.parties = parties; this.count = parties; // 初始值为 parties this.barrierCommand = barrierAction;}
wait 方法
1、无参的wait 方法,如果发生中断会抛出中断异常,如果屏障被破坏会抛出屏障破坏异常
public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen }}
2、拥有超时功能的await,达到屏障后,进行一定时间的等待,如果在这段时间内,屏障还没有结束(所有线程到达屏障),则会抛出超时异常。
public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { return dowait(true, unit.toNanos(timeout));}
dowait方法为CyclicBarrier类的核心方法,CyclicBarrier类对外提供的await方法在底层都是调用该了方法。
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(); } // 正在等待进入屏障的线程数量-1 int index = --count; if (index == 0) { //所有线程都已经进入屏障了,该线程是最后一个进入屏障的 // 执行任务的标识 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 { //当前屏障已经结束,或者屏障已经被破坏,则重设中断标识位 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(); //释放锁 }}
整个逻辑已经还是比较清楚了,还是再进行简单的描述:
当线程来屏障时(dowait),如果屏障已经被破坏,那么直接抛出异常,如果发现发生了中断异常,则表示上层调用有情况,破坏屏障,抛出异常。
如果自己是最后一个到达屏障的线程,那么需要执行指定的任务(如果有的话),如果执行过程中,发生异常,则破坏屏障(相当于通知了其它线程)。否则结束当前屏障,返回。
如果自己不是最后一个到达屏障的线程,如果设置了超时,则进行超时等待,否则进行”无限”等待,如果在等待过程中,发生了中断异常,如果当前屏障没有结束,并且没有被破坏,那么破坏屏障,抛出异常,否则重新设置中断标志位,告诉上层应用,曾经被中断过。
当从等待中醒来时,如果发现屏障被破坏了,那么抛出异常,如果屏障没有被破坏,并且屏障已经换代了,那么说明最后一个线程已经到来了,当前屏障任务结束,返回来到屏障的相对顺序。
如果屏障还没有结束,同时设置了超时等待,并且已经超时,则抛出超时异常,否则,否则再次从循环开始(检查条件,等待)。
从dowait 我们看到,如果当前线程无法进行执行或者等待,那么就会通过破坏屏障的方式来通知其它线程(也表示了本次屏障失败)。
当最后一个线程到来时,应该负责唤醒其它等待的线程,这个操作是放在的nextGeneration 方法中的,再看这个方法之前,我们来看看breakBarrier 这个方法。
breakBarrier 方法
private void breakBarrier() { generation.broken = true; // 设置屏障破坏标识 count = parties; //重设count trip.signalAll(); //唤醒所有等待线程}
如果当前线程不能继续等待或者执行,那么就会破坏线程,同时也会唤醒阻塞在屏障中的线程,关于唤醒的工作,我们后面再来分析。
nextGeneration 方法
private void nextGeneration() { // signal completion of last generation trip.signalAll(); //唤醒阻塞在屏障上的所有线程 // set up next generation count = parties; //重设count generation = new Generation(); // 生成新的屏障}
当最后一个线程到底屏障,并且任务执行成功后(如果有任务的话),那么就表示当前屏障执行完毕,就会唤醒其它线程,继续往后执行,同时重新生成新的屏障,以供下次使用。
signalAll 方法
这个signalAll 是同步器中的方法,这个我们这里再来撸一遍吧
public final void signalAll() { if (!isHeldExclusively()) // 当前线程必须处于独占模式(获取排它锁) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) //如果条件队列不为空 doSignalAll(first); //唤醒所有等待线程}
Condition维护着一个队列,凡是不满足该条件的都会放到该队列进行等待。
signalAll方法会判断头结点是否为空,即条件队列是否为空,然后会调用doSignalAll函数
private void doSignalAll(Node first) { lastWaiter = firstWaiter = null; // 置空 //遍历队列,唤醒每一个节点 do { Node next = first.nextWaiter; first.nextWaiter = null; // 将first结点从condition队列转移到sync队列 transferForSignal(first); first = next; } while (first != null);}
doSignalAll 则是遍历条件队列,将每个节点转移到同步队列中,然后执行再执行唤醒工作,下面看看transferForSignal 方法(该方法在:Java 并发 —AbstractQueuedSynchronizer-共享模式与Condition 有分析,因此下面分析的相对比较简略,如果不是很懂,可以参考这篇文章的内容)
final boolean transferForSignal(Node node) { /* * If cannot change waitStatus, the node has been cancelled. */ if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) //将节点等待状态设置为0 return false; /* * Splice onto queue and try to set waitStatus of predecessor to * indicate that thread is (probably) waiting. If cancelled or * attempt to set waitStatus fails, wake up to resync (in which * case the waitStatus can be transiently and harmlessly wrong). */ Node p = enq(node); // 将节点加入同步队列中 int ws = p.waitStatus; //当前节点通过CAS机制将waitStatus置为SIGNAL if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); // 唤醒节点线程 return true;}
看到这里,应该就明白了CyclicBarrier 的大致流程吧,当屏障执行成功,最后一个线程会唤醒所有等待线程,唤醒的线程会加入到同步队列中,如果屏障执行失败,则也会唤醒所有等待的线程。
这里还有一个细节,把等待的线程从Condition队列移到同步队列中后,线程还没有真正被唤醒,同步队列中的线程会竞争获取同步状态(锁),只有获取到同步状态后,线程才会真正的唤醒,这个部分可以参考同步器文章的内容,这里就不在详细展开了。
这里还有一个细节,当所有线程到底屏障后,最后一个线程执行指定的任务:
//需要执行的任务final Runnable command = barrierCommand;if (command != null) command.run(); //执行任务
这里执行的run 方法,意味着默认这样操作是同步执行的,并不是单独建立一个线程来执行任务,当然我们的run 方法中,可以自己建立线程来异步执行该任务。
总结
CyclicBarrier 主要用 ReeantrantLock 与 Condition 来控制线程资源的获取。
CyclicBarrier 是可以被重新利用的,当屏障结束后,屏障会重新被初始化(或者手动重置)。
如果指定了任务,这个任务是由最后一个进入屏障的线程来执行的,调用的是run 方法,也就是默认是进行同步调用执行的。
当CyclicBarrier 执行成功时,会唤醒阻塞在屏障中的线程,等待的线程会移到同步队列中,参与同步状态(锁)的竞争。
当CyclicBarrier 执行失败时(中断,超时,指定的任务执行失败),也会唤醒阻塞在屏障的线程,只是这里会抛出异常。
想要了解更多,可以参考下面的内容:
Java 并发 — CountDownLatch源码分析
Java 并发 —ReentrantLock源码分析
Java 并发 —AbstractQueuedSynchronizer(同步器)-独占模式
Java 并发 —AbstractQueuedSynchronizer-共享模式与Condition
- Java 并发 --- CyclicBarrier源码分析
- 【Java8源码分析】并发包-CyclicBarrier
- 《Java源码分析》:CyclicBarrier(part one)
- 《Java源码分析》:CyclicBarrier(part two)
- Java多线程 -- JUC包源码分析11 -- CyclicBarrier源码分析
- Java并发编程--CyclicBarrier
- Java并发同步器--CyclicBarrier
- Java并发之CyclicBarrier
- Java并发之CyclicBarrier
- java并发中的CyclicBarrier
- java 并发之 CyclicBarrier
- 源码分析-CyclicBarrier
- CyclicBarrier源码分析
- jdk 源码分析(16)java CyclicBarrier 源码解析
- Java多线程系列(八)—CyclicBarrier源码分析
- java并发编程之CyclicBarrier
- JAVA并发编程之 CyclicBarrier
- java并发:CyclicBarrier的使用
- (4.1.2)基础总结篇之一:Activity生命周期
- GitHub账户设置多个SSH Keys
- VirtualBox中Ubuntu共享windows文件夹
- java入门04
- Redis安全注意事项
- Java 并发 --- CyclicBarrier源码分析
- 阿里云安装jdk-tomcat-mysql
- 前端面经-webpack
- 分答项目_知识点:h5自定义属性的jquery获取方法
- Saruman's Army (贪心)
- JavaScript 面向对象之 原型-prototype
- java详解 --- 随机数、函数及数组部分
- JPA
- Java知识点与BUG的汇总(个人整理的)以及一些代码优化的方法、项目结构剖析的描述