一.CountDownLatch

  1.概念

    public CountDownLatch(int count) {//初始化
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}

  CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

  CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

  下面有A、B、C、D4个线程同时执行,A是主线程,B、C、D是子线程,A先开始执行后阻塞,等待子线程全部执行结束才继续执行剩下的任务。

  2.用法

  1)、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown(),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。

    public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(3);
final CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("子线程" + Thread.currentThread().getName() + "开始执行");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("子线程"+Thread.currentThread().getName()+"执行完成");
latch.countDown();//当前线程调用此方法,则计数减一
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
} try {
System.out.println("主线程"+Thread.currentThread().getName()+"等待子线程执行完成...");
latch.await();//阻塞当前线程,直到计数器的值为0
System.out.println("主线程"+Thread.currentThread().getName()+"开始执行...");
} catch (InterruptedException e) {
e.printStackTrace();
}
} 结果:

主线程main等待子线程执行完成...
子线程pool-1-thread-1开始执行
子线程pool-1-thread-3开始执行
子线程pool-1-thread-2开始执行
子线程pool-1-thread-3执行完成
子线程pool-1-thread-1执行完成
子线程pool-1-thread-2执行完成
主线程main开始执行...

  2)、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。

    public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CountDownLatch cdOrder = new CountDownLatch(1);
final CountDownLatch cdAnswer = new CountDownLatch(4);
for (int i = 0; i < 4; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("选手" + Thread.currentThread().getName() + "正在等待裁判发布口令");
cdOrder.await();
System.out.println("选手" + Thread.currentThread().getName() + "已接受裁判口令");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("选手" + Thread.currentThread().getName() + "到达终点");
cdAnswer.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("裁判"+Thread.currentThread().getName()+"即将发布口令");
cdOrder.countDown();
System.out.println("裁判"+Thread.currentThread().getName()+"已发送口令,正在等待所有选手到达终点");
cdAnswer.await();
System.out.println("所有选手都到达终点");
System.out.println("裁判"+Thread.currentThread().getName()+"汇总成绩排名");
} catch (InterruptedException e) {
e.printStackTrace();
}
service.shutdown();
} 结果:

选手pool-1-thread-2正在等待裁判发布口令
选手pool-1-thread-1正在等待裁判发布口令
选手pool-1-thread-3正在等待裁判发布口令
选手pool-1-thread-4正在等待裁判发布口令
裁判main即将发布口令
裁判main已发送口令,正在等待所有选手到达终点
选手pool-1-thread-2已接受裁判口令
选手pool-1-thread-1已接受裁判口令
选手pool-1-thread-3已接受裁判口令
选手pool-1-thread-4已接受裁判口令
选手pool-1-thread-2到达终点
选手pool-1-thread-1到达终点
选手pool-1-thread-4到达终点
选手pool-1-thread-3到达终点
所有选手都到达终点
裁判main汇总成绩排名

  3.countDown解析

  递减锁存器的计数,如果计数到达零,则释放所有等待的线程。如果当前计数大于零,则将计数减少.

    public void countDown() {
sync.releaseShared(1);
}

  countDown调用AQS的releaseShared

    public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//数量为0
doReleaseShared();//唤醒其他等待线程
return true;
}
return false;
}
protected boolean tryReleaseShared(int arg) {//releaseShared调用,由CountDownLatch的内部类Sync实现
throw new UnsupportedOperationException();
}
private static final class Sync extends AbstractQueuedSynchronizer {//CountDownLatch的内部类Sync
protected boolean tryReleaseShared(int releases) {
for (;;) {//自旋,count不断-1,直到为0则发起唤醒信号
int c = getState();//获得数量,在CountDownLatch(int count)初始化时定义了数量
if (c == 0)//数量为0则返回false
return false;
int nextc = c-1;//数量-1
//CAS更新状态,nextc为0返回true
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}

  4.await解析

  使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。如果当前计数为零,则唤醒阻塞线程。

  如果当前计数大于零,则出于线程调度目的,将禁用当前线程,该线程将一直出于休眠状态;

public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);//由AQS实现
}
/**
* Acquires in shared mode, aborting if interrupted. Implemented
* by first checking interrupt status, then invoking at least once
* {@link #tryAcquireShared}, returning on success. Otherwise the
* thread is queued, possibly repeatedly blocking and unblocking,
* invoking {@link #tryAcquireShared} until success or the thread
* is interrupted.
* 以共享模式获取,如果中断被中止。
* 实现首先检查中断状态,然后至少调用一次tryacquirered,成功返回。
* 否则,线程排队,可能会重复阻塞和取消阻塞,
* 调用tryacquiremred直到成功或线程被打断了。
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())//有中断抛出异常
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//CountDownLatch的Sync实现,计数数量不为0,表示有线程需要阻塞
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//以共享中断模式获取
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);//创建当前线程的节点,并且锁的模型是共享锁,将其添加到AQS CLH队列的末尾
boolean failed = true;
try {
for (;;) {//自旋
final Node p = node.predecessor();//获得当前节点的前驱节点
if (p == head) {//是头节点,没有等待节点
int r = tryAcquireShared(arg);
if (r >= 0) {//子线程都执行完成了,原先阻塞线程唤醒执行
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//前继节点非head节点,没资源获取,将前继节点状态设置为SIGNAL,通过park挂起node节点的线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);//结束该节点线程的请求
}
}

二.CyclicBarrier

  1.概念

    public CyclicBarrier(int parties, Runnable barrierAction) {//初始化
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
} public CyclicBarrier(int parties) {//初始化
this(parties, null);
}

  允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。

  A CyclicBarrier支持一个可选的Runnable命令,每个屏障点运行一次,在派对中的最后一个线程到达之后,但在任何线程释放之前。 在任何一方继续进行之前,此屏障操作对更新共享状态很有用。

  CyclicBarrier当计数减少到0时,会唤醒所有阻塞在同一个Condition上的线程,与CountDownLatch不同的是所有的线程必须同时被唤醒,就好比钓鱼比赛,所有人必须同时开始抛竿一样。CountDownLatch只要求主线程的动作在其他依赖的线程执行完之后执行就OK。

  下面有A、B、C、D4个线程同时执行,每个线程有任务a、b,每个线程的每个任务执行完才开始继续下个任务执行。

  2.用法

public class CyclicBarrierTest {
public static void main(String[] args) {
int count = 10;//并发线程数
CyclicBarrier cyclicBarrier = new CyclicBarrier(count);
ExecutorService executorService = Executors.newFixedThreadPool(count);
int n = 1;
for (int i = 0; i < count; i++) {
executorService.execute(new Task(cyclicBarrier, n));
n++;
}
executorService.shutdown(); // 关闭线程池
// 判断是否所有的线程已经运行完
while (!executorService.isTerminated()) {
try {
// 所有线程池中的线程执行完毕,执行后续操作
System.out.println(" ==============is sleep============");
Thread.sleep(10000);
System.out.println(" ==============is wake============"); } catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} class Task implements Runnable {
private CyclicBarrier cyclicBarrier;
int n = 0; public Task(CyclicBarrier cyclicBarrier, int n) {
this.cyclicBarrier = cyclicBarrier;
this.n = n;
} @Override
public void run() {
try {
System.out.println("赛马" + n + "到达栅栏前");
cyclicBarrier.await();
System.out.println("赛马" + n + "开始跑");
cyclicBarrier.await();
System.out.println("赛马" + n + "到达终点");
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:

==============is sleep============
赛马2到达栅栏前
赛马3到达栅栏前
赛马4到达栅栏前
赛马1到达栅栏前
赛马5到达栅栏前
赛马6到达栅栏前
赛马7到达栅栏前
赛马8到达栅栏前
赛马9到达栅栏前
赛马10到达栅栏前
赛马10开始跑
赛马3开始跑
赛马2开始跑
赛马4开始跑
赛马1开始跑
赛马6开始跑
赛马7开始跑
赛马5开始跑
赛马9开始跑
赛马8开始跑
赛马8到达终点
赛马2到达终点
赛马3到达终点
赛马4到达终点
赛马7到达终点
赛马10到达终点
赛马5到达终点
赛马6到达终点
赛马1到达终点
赛马9到达终点
==============is wake============

 

  3.await解析

  如果当前线程不是最后一个线程,那么它被禁用以进行线程调度,并且处于休眠状态,直到发生下列事情之一:

  • 最后一个线程到达; 要么
  • 一些其他线程当前线程为interrupts ; 要么
  • 一些其他线程interrupts其他等待线程之一; 要么
  • 一些其他线程在等待屏障时超时; 要么
  • 其他一些线程在这个屏障上调用reset()

  CyclicBarrier的原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用await方法时,将拦截的线程数减1,然后判断剩余拦截数是否为初始值parties,如果不是,进入Lock对象的条件队列等待。如果是,执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁。

        public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;//获锁
lock.lock();//加锁
try {
//当代,每个屏障都会创建一个Generation实例
final Generation g = generation; if (g.broken)//当代遭到破坏抛出异常
throw new BrokenBarrierException(); if (Thread.interrupted()) {//线程中断抛出异常
// 将损坏状态设置为true,并通知其他阻塞在此栅栏上的线程
breakBarrier();
throw new InterruptedException();
} int index = --count;//获取下标并-1
if (index == 0) { //最后一个线程到达了
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();//执行栅栏任务
ranAction = true;
nextGeneration();// 更新一代,将count重置,将generation重置,唤醒之前等待的线程
return 0;
} finally {
// 如果执行栅栏任务的时候失败了,就将损坏状态设置为true
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();
}
}
// 当有任何一个线程中断了,就会调用breakBarrier方法,
//就会唤醒其他的线程,其他线程醒来后,也要抛出异常
if (g.broken)
throw new BrokenBarrierException();
// g != generation表示正常换代了,返回当前线程所在栅栏的下标
// 如果 g == generation,说明还没有换代,那为什么会醒了?
// 因为一个线程可以使用多个栅栏,当别的栅栏唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。
// 正是因为这个原因,才需要generation来保证正确。
if (g != generation)
return index;
// 如果有时间限制,且时间小于等于0,销毁栅栏并抛出异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}

  dowait(boolean, long)方法的主要逻辑处理比较简单,如果该线程不是最后一个调用await方法的线程,则它会一直处于等待状态,除非发生以下情况:

  • 最后一个线程到达,即index == 0

  • 某个参与线程等待超时

  • 某个参与线程被中断

  • 调用了CyclicBarrier的reset()方法。该方法会将屏障重置为初始状态

  在上面的源代码中,我们可能需要注意Generation 对象,在上述代码中我们总是可以看到抛出BrokenBarrierException异常,那么什么时候抛出异常呢?如果一个线程处于等待状态时,如果其他线程调用reset(),或者调用的barrier原本就是被损坏的,则抛出BrokenBarrierException异常。同时,任何线程在等待时被中断了,则其他所有线程都将抛出BrokenBarrierException异常,并将barrier置于损坏状态。

  同时,Generation描述着CyclicBarrier的更新换代。在CyclicBarrier中,同一批线程属于同一代。当有parties个线程到达barrier之后,generation就会被更新换代。其中broken标识该当前CyclicBarrier是否已经处于中断状态。
注意事项:

  • CyclicBarrier使用独占锁来执行await方法,并发性可能不是很高。

  • 如果在等待过程中,线程被中断了,就抛出异常。但如果中断的线程所对应的CyclicBarrier不是这代的,比如,在最后一次线程执行signalAll后,并且更新了这个“代”对象。在这个区间,这个线程被中断了,那么,JDK认为任务已经完成了,就不必在乎中断了,只需要打个标记。该部分源码已在dowait(boolean, long)方法中进行了注释。

  • 如果线程被其他的CyclicBarrier唤醒了,那么g肯定等于generation,这个事件就不能return了,而是继续循环阻塞。反之,如果是当前CyclicBarrier唤醒的,就返回线程在CyclicBarrier的下标。完成了一次冲过栅栏的过程。该部分源码已在dowait(boolean, long)方法中进行了注释。

参考:

  CyclicBarrier:https://blog.csdn.net/qq_38293564/article/details/80558157

多线程高并发编程(5) -- CountDownLatch、CyclicBarrier源码分析的更多相关文章

  1. 多线程高并发编程(8) -- Fork/Join源码分析

    一.概念 Fork/Join就是将一个大任务分解(fork)成许多个独立的小任务,然后多线程并行去处理这些小任务,每个小任务处理完得到结果再进行合并(join)得到最终的结果. 流程:任务继承Recu ...

  2. Java并发编程笔记之CyclicBarrier源码分析

    JUC 中 回环屏障 CyclicBarrier 的使用与分析,它也可以实现像 CountDownLatch 一样让一组线程全部到达一个状态后再全部同时执行,但是 CyclicBarrier 可以被复 ...

  3. Java并发编程笔记之ThreadLocalRandom源码分析

    JDK 并发包中 ThreadLocalRandom 类原理剖析,经常使用的随机数生成器 Random 类的原理是什么?及其局限性是什么?ThreadLocalRandom 是如何利用 ThreadL ...

  4. Java并发编程笔记之PriorityBlockingQueue源码分析

    JDK 中无界优先级队列PriorityBlockingQueue 内部使用堆算法保证每次出队都是优先级最高的元素,元素入队时候是如何建堆的,元素出队后如何调整堆的平衡的? PriorityBlock ...

  5. Java并发编程笔记之ArrayBlockingQueue源码分析

    JDK 中基于数组的阻塞队列 ArrayBlockingQueue 原理剖析,ArrayBlockingQueue 内部如何基于一把独占锁以及对应的两个条件变量实现出入队操作的线程安全? 首先我们先大 ...

  6. Java并发编程笔记之AbstractQueuedSynchronizer源码分析

    为什么要说AbstractQueuedSynchronizer呢? 因为AbstractQueuedSynchronizer是JUC并发包中锁的底层支持,AbstractQueuedSynchroni ...

  7. Java并发编程笔记之CopyOnWriteArrayList源码分析

    并发包中并发List只有CopyOnWriteArrayList这一个,CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行修改操作和元素迭代操作都是在底层创建一个拷贝 ...

  8. Java并发编程笔记之ThreadLocal源码分析

    多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各个操作的顺序是不可预期的,多线程访问同一个共享变量特别容易出现并发问题,特别是多个线程需要对一个共享变量进行写入时候, ...

  9. Java并发编程中线程池源码分析及使用

    当Java处理高并发的时候,线程数量特别的多的时候,而且每个线程都是执行很短的时间就结束了,频繁创建线程和销毁线程需要占用很多系统的资源和时间,会降低系统的工作效率. 参考http://www.cnb ...

随机推荐

  1. 微信开发+百度AI学习:微信网页开发环境搭建

    参考微信官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115 两步即可获取微信网页开发能力 STEP1: ...

  2. Servlet(五)----ServletContext对象

    ##  ServletContext对象 1.概念:代表整个web应用,可以和程序的容器(服务器)来通信 2.获取: 1.通过request对象获取 request.getServletContext ...

  3. CF33C Wonderful Randomized Sum 题解

    原题链接 简要题意: 你可以无限次的把该数组的一个前缀和后缀 \(\times -1\),问最终的最大序列和. 这题盲目WA了数次才知道本质 这题89个数据吊打std CF真好啊,发现一个错后面就不测 ...

  4. Ubuntu18.04下安装MySQL5.7(支持win10-wsl环境)

    注意: 本文操作环境为win10系统wsl下的Ubuntu18.04,对于原生的Ubuntu18.04同样适用.MySQL默认版本为5.7,其他版本不适用. 安装步骤 1.更新源: sudo apt ...

  5. 2020年PHP 面试问题(三)

    2020年PHP 面试问题(一) 2020年PHP 面试问题(二) 一.数据库三范式 第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解: 第二范式:2NF是对记录的惟一性约束,要求 ...

  6. 10行Python代码计算汽车数量

    当你还是个孩子坐车旅行的时候,你玩过数经过的汽车的数目的游戏吗? 在这篇文章中,我将教你如何使用10行Python代码构建自己的汽车计数程序. 以下是环境及相应的版本库: Python版本 3.6.9 ...

  7. Spring Boot 中自定义 SpringMVC 配置,到底继承谁哪一个类或则接口?

    看了这篇文章,写的非常的言简意赅,特此记录下: 1.Spring Boot 1.x 中,自定义 SpringMVC 配置可以通过继承 WebMvcConfigurerAdapter 来实现. 2.Sp ...

  8. linux 之虚拟机的安装与介绍

    linux 零基础入门1.1linux介绍 操作系统用途: 管理硬件 驱动硬件 管理软件 分配资源1.2 linux的发展unix -> windows ->linuxlinux 免费 开 ...

  9. 基于 HTML5 WebGL 的楼宇智能化集成系统(二)

    前言       一套完整的可视化操作交互上,必不可少 2D/3D 的融合,在上期我们介绍了有关 3D 场景的环视漫游.巡视漫游以及动画效果,还包括了冷站场景.热站场景以及智慧末端的实现原理,本期主要 ...

  10. java+lodop+vue+热敏打印机,打印图片

    1.根据需求生成图片模板,详情见 https://www.cnblogs.com/xiaokangk/p/11151774.html 2.下载lodop并进行安装(安装步骤详情百度) 3.安装热敏打印 ...