java线程并发控制同步工具CountDownLatch

目录
  • 前言
  • 了解CountDownLatch
    • 思考问题:
    • 主要参数与方法
    • 构造方法
  • CountDownLatch底层实现原理
  • 执行流程图
  • 实践
    • 用法一:
    • 用法二:
  • 总结

前言

大家好,我是小郭,前面我们学习了利用Semaphore来防止多线程同时操作一个资源,通常我们都会利用并行来优化性能,但是对于串行化的业务,可能需要按顺序执行,那我们怎么才能处理呢?今天我们来学习另一个并发流程控制的同步工具CountDownLatch。

了解CountDownLatch

首先,CountDownLatch是一种并发流程控制的同步工具。

主要的作用是等待多个线程同时完成任务之后,再继续完成主线程任务。

简单点可以理解为,几个小伙伴一起到火锅店聚餐,人到齐了,火锅店才可以开饭。

思考问题:

  • CountDownLatch 底层原理是什么,他是否可以代替wait / notify?
  • CountDwonLatch 业务场景有哪些?
  • 一次可以唤醒多个任务吗?

主要参数与方法

//减少锁存器的计数,如果计数达到零,则释放所有等待线程。
//计数器
public void countDown() {
    sync.releaseShared(1);
}
//导致当前线程等待,直到锁存器递减至零为止,除非该线程被中断。
//火锅店调用await的线程,count为0才能继续执行
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

构造方法

//count 数量,理解为小伙伴的个数
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
}
//获取剩余的数量
public long getCount() {
    return sync.getCount();
}

CountDownLatch底层实现原理

我们可以看出countDown()是CountDownLatch的核心方法,我来看下他的具体实现。

CountDownLatch来时继承AQS的共享模式来完成其的实现,从前面的学习得出AQS主要是依赖同步队列和state实现控制。

共享模式:

这里与独占锁大多数相同,自旋过程中的退出条件是是当前节点的前驱节点是头结点并且tryAcquireShared(arg)返回值大于等于0即能成功获得同步状态.

await

public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//当状态不为0挂起,表示当前线程被占有,需要线程排队
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}
//在共享模式下获取
doAcquireSharedInterruptibly(int arg)

countDown

public void countDown() {
    sync.releaseShared(1);
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
//自旋防止失败
    for (;;) {
        //获取状态
        int c = getState();
        //状态为为0返回false,表示没有被线程占有
        if (c == 0) return false;
        //调用cas来进行替换,也保证了线程安全,当为0的时候唤醒
        int nextc = c-1;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
//当任务数量为0,aqs的释放共享锁
void doReleaseShared()
private void doReleaseShared() {
    /*
        * Ensure that a release propagates, even if there are other
        * in-progress acquires/releases.  This proceeds in the usual
        * way of trying to unparkSuccessor of head if it needs
        * signal. But if it does not, status is set to PROPAGATE to
        * ensure that upon release, propagation continues.
        * Additionally, we must loop in case a new node is added
        * while we are doing this. Also, unlike other uses of
        * unparkSuccessor, we need to know if CAS to reset status
        * fails, if so rechecking.
        */
    // 无限循环
    for (;;) {
        // 保存头节点
        Node h = head;
        // 头节点不为空并且头节点不为尾结点
        if (h != null && h != tail) {
            // 获取头节点的等待状态
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                // 状态为SIGNAL,CAS更新状态
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 释放后继结点
                unparkSuccessor(h);
            }
            // 状态为0并且更新不成功,继续
            else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) //
                continue;                // loop on failed CAS
        }
        if (h == head) // 若头节点改变,继续循环
            break;
    }
}

思考

  • 如何安排线程排序

个人认为,没有进行线程的排序,而是让一部分线程进入等待,在唤醒的时候放开。

执行流程图

实践

用法一:

一个线程等待其他多个线程都执行完毕,再继续自己的工作

public class CountDownLatchTest {
    private static Lock lock = new ReentrantLock();
    private static CountDownLatch countDownLatch = new CountDownLatch(4);
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        IntStream.range(0,16).forEach(i -&gt;{
            executorService.submit(()-&gt;{
                lock.lock();
                System.out.println(Thread.currentThread().getName()+ "来火锅店吃火锅!");
                try {
                    Thread.sleep(1000);
                    countDownLatch.countDown();
                    System.out.println(Thread.currentThread().getName() + "我到火锅店了,准备开吃!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            });
        });
        try {
            countDownLatch.await(5,TimeUnit.SECONDS);
            System.out.println("人到齐了,开饭");
            executorService.shutdown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出结果

代码中设置了一个CountDownLatch做倒计时,四个人(count为4)一起到火锅店吃饭,每到一个人计数器就减去1(countDownLatch.countDown()),当计数器为0的时候,main线程在await的阻塞结束,继续往下执行。

用法二:

多个线程等待某一个线程的信号,同时开始执行

用抢位子作为例子,将线程挂起等待,同时开始执行。

public class CountDownLatchTest2 {
    private static Lock lock = new ReentrantLock();
    private static CountDownLatch countDownLatch = new CountDownLatch(1);
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        IntStream.range(0,4).forEach(i ->{
            executorService.submit(()->{
                System.out.println(Thread.currentThread().getName()+ "准备开始抢位子!");
                try {
                    //Thread.sleep(1000);
                    countDownLatch.await();
                    System.out.println(Thread.currentThread().getName() + "抢到了位置");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        });
        try {
            Thread.sleep(5000);
            System.out.println("五秒后开始抢位置");
            countDownLatch.countDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
    }
}

注意点

CountDownLatch是不能重用的。

总结

我们可以看到CountDownLatch的使用很简单,就当做一个计时器来使用,在控制并发方面能给我们提供帮助。

  • 在构造器中初始化任务数量
  • 调用await()挂起主线程main
  • 调用countDown()方法减一,直到为0的时候,唤醒主线程可以继续运行。

上面提供的两个用法,我们也可以结合起来使用。

在实际的业务代码开发中,利用CountDownLatch来进行业务方法的执行,来确定他们的顺序,解决一个线程等待多个线程的场景

以上就是java线程并发控制同步工具CountDownLatch的详细内容,更多关于java线程并发CountDownLatch的资料请关注我们其它相关文章!

(0)

相关推荐

  • java并发包中CountDownLatch和线程池的使用详解

    1.CountDownLatch 现在做的这个华为云TaurusDB比赛中,参考的之前参加过阿里的PolarDB大赛的两个大佬的代码,发现都有用到CountDownLatch这个类,之前看代码的时候也看过,但是没有搞得很明白,自己写也写不出来,在此自己先学习一下. 字面理解:CountDownLatch:数量减少的门栓. 创建这样一个门栓 CountDownLatch countDownLatch = new CountDownLatch(count); 参数:count,门栓的计数次数. 在所

  • Java并发系列之CountDownLatch源码分析

    CountDownLatch(闭锁)是一个很有用的工具类,利用它我们可以拦截一个或多个线程使其在某个条件成熟后再执行.它的内部提供了一个计数器,在构造闭锁时必须指定计数器的初始值,且计数器的初始值必须大于0.另外它还提供了一个countDown方法来操作计数器的值,每调用一次countDown方法计数器都会减1,直到计数器的值减为0时就代表条件已成熟,所有因调用await方法而阻塞的线程都会被唤醒.这就是CountDownLatch的内部机制,看起来很简单,无非就是阻塞一部分线程让其在达到某个条

  • Java线程并发工具类CountDownLatch原理及用法

    一.CountDownLatch [1]CountDownLatch是什么? CountDownLatch,英文翻译为倒计时锁存器,是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或 多个线程一直等待. 闭锁可以延迟线程的进度直到其到达终止状态,闭锁可以用来确保某些活动直到其他活动都完成才继续执行: 确保某个计算在其需要的所有资源都被初始化之后才继续执行; 确保某个服务在其依赖的所有其他服务都已经启动之后才启动; 等待直到某个操作所有参与者都准备就绪再继续执行: CountD

  • Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解

    Java并发编程:CountDownLatch与CyclicBarrier和Semaphore的实例详解 在java 1.5中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier和Semaphore,今天我们就来学习一下这三个辅助类的用法. 以下是本文目录大纲: 一.CountDownLatch用法 二.CyclicBarrier用法 三.Semaphore用法 若有不正之处请多多谅解,并欢迎批评指正. 一.CountDownLatch

  • java线程并发countdownlatch类使用示例

    复制代码 代码如下: package com.yao; import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors; /** * CountDownLatch是个计数器,它有一个初始数, * 等待这个计数器的线程必须等到计数器倒数到零时才可继续. */public class CountDownLatchTe

  • java并发使用CountDownLatch在生产环境翻车剖析

    目录 前言 需求背景 具体实现 解决方案 总结 前言 大家好,我是小郭,之前分享了CountDownLatch的使用,我们知道用来控制并发流程的同步工具,主要的作用是为了等待多个线程同时完成任务后,在进行主线程任务. 万万没想到,在生产环境中竟然翻车了,因为没有考虑到一些场景,导致了CountDownLatch出现了问题,接下来来分享一下由于CountDownLatch导致的问题. [线程]并发流程控制的同步工具-CountDownLatch 需求背景 先简单介绍下业务场景,针对用户批量下载的文

  • java线程并发控制同步工具CountDownLatch

    目录 前言 了解CountDownLatch 思考问题: 主要参数与方法 构造方法 CountDownLatch底层实现原理 执行流程图 实践 用法一: 用法二: 总结 前言 大家好,我是小郭,前面我们学习了利用Semaphore来防止多线程同时操作一个资源,通常我们都会利用并行来优化性能,但是对于串行化的业务,可能需要按顺序执行,那我们怎么才能处理呢?今天我们来学习另一个并发流程控制的同步工具CountDownLatch. 了解CountDownLatch 首先,CountDownLatch是

  • Java多线程之同步工具类CountDownLatch

    前言: CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行.例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有框架服务之后执行. 1 CountDownLatch主要方法 void await():如果当前count大于0,当前线程将会wait,直到count等于0或者中断. PS:当count等于0的时候,再去调用await() , 线程将不会阻塞,而是立即运行.后面可以通过源码分析得到. boolean await(long t

  • Java线程的并发工具类实现原理解析

    目录 一.fork/join 1. Fork-Join原理 2. 工作窃取 3. 代码实现 二.CountDownLatch 三.CyclicBarrier 四.Semaphore 五.Exchange 六.Callable.Future.FutureTask 在JDK的并发包里提供了几个非常有用的并发工具类.CountDownLatch.CyclicBarrier和Semaphore工具类提供了一种并发流程控制的手段,Exchanger工具类则提供了在线程间交换数据的一种手段.本章会配合一些应

  • Java多线程之同步工具类CyclicBarrier

    目录 1 CyclicBarrier方法说明 2 CyclicBarrier实例 3 CyclicBarrier源码解析 CyclicBarrier构造函数 await方法 nextGeneration的源码 breakBarrier源码 isBroken方法 reset方法 getNumberWaiting方法 前言: CyclicBarrier是一个同步工具类,它允许一组线程互相等待,直到达到某个公共屏障点.与CountDownLatch不同的是该barrier在释放线程等待后可以重用,所以

  • Java多线程之同步工具类Exchanger

    目录 1 Exchanger 介绍 2 Exchanger 实例 exchange等待超时 3 实现原理 1 Exchanger 介绍 前面分别介绍了CyclicBarrier.CountDownLatch.Semaphore,现在介绍并发工具类中的最后一个Exchange. Exchanger 是一个用于线程间协作的工具类,Exchanger用于进行线程间的数据交换,它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据.这两个线程通过exchange 方法交换数据,如果第一个线程先执行e

  • Java线程Dump分析工具jstack解析及使用场景

    jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式: jstack [-l][F] pid 如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题.另外,jstack工具还可以附属到正在运行的j

  • Java中多线程同步类 CountDownLatch

    在多线程开发中,常常遇到希望一组线程完成之后在执行之后的操作,java提供了一个多线程同步辅助类,可以完成此类需求: 类中常见的方法: 其中构造方法: CountDownLatch(int count) 参数count是计数器,一般用要执行线程的数量来赋值. long getCount():获得当前计数器的值. void countDown():当计数器的值大于零时,调用方法,计数器的数值减少1,当计数器等数零时,释放所有的线程. void await():调所该方法阻塞当前主线程,直到计数器减

  • Java线程使用同步锁交替执行打印奇数偶数的方法

    对同一个对象进行多线程操作时,如何保证线程执行结果的一致性?我们需要对线程操作对象加同步锁.(这是一道面试题) 需求描述 1-20个数字 A线程打印奇数:1,3,5,7,9,11,13,15,17,19 B线程打印偶数:2,4,6,8,10,12,14,16,18,20 C线程在AB两个线程执行完了之后打印结果:"success". 线程代码实现 Num.java package com.boonya.thread.test; /** * @ClassName: Num * @Desc

  • Java编程线程同步工具Exchanger的使用实例解析

    本文研究的主要是Java编程线程同步工具Exchanger的使用,下面看看具体内容. 如果两个线程在运行过程中需要交换彼此的信息,比如一个数据或者使用的空间,就需要用到Exchanger这个类,Exchanger为线程交换信息提供了非常方便的途径,它可以作为两个线程交换对象的同步点,只有当每个线程都在进入 exchange ()方法并给出对象时,才能接受其他线程返回时给出的对象. 每次只能两个线程交换数据,如果有多个线程,也只有两个能交换数据.下面看个通俗的例子:一手交钱一首交货! public

  • Java线程创建(卖票),线程同步(卖包子)的实现示例

    1.线程两种创建方式:new Thread(new Runnable() {}) 如下FileOutputStream源码中抛出异常,为了让写代码人自己写try catch异常提示信息. package com.itheim07.thread; /* * 进程和线程 * 1. 进程 : 航空母舰(资源: 燃油 弹药) * 2. 线程 : 舰载机 * 一个软件运行: 一个军事活动, 必须有一艘航母出去,但执行具体任务的是航母上的舰载机 * 一个软件运行,至少一个进程, 一个进程中至少一个线程.谷歌

随机推荐