Java并发工具类之CountDownLatch详解

 更新时间:2023年12月27日 09:01:22   作者:缘来如此09  
这篇文章主要介绍了Java并发工具类之CountDownLatch详解,CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行,CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景,需要的朋友可以参考下

CountDownLatch

1.概述

CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。

CountDownLatch 定义了一个计数器,和一个阻塞队列, 当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,这里的计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器,CountDownLatch可以解决那些一个或者多个线程在执行之前必须依赖于某些必要的前提业务先执行的场景。

2.常用方法

CountDownLatch(int count); //构造方法,创建一个值为count 的计数器。
​
await();//阻塞当前线程,将当前线程加入阻塞队列。
​
await(long timeout, TimeUnit unit);//在timeout的时间之内阻塞当前线程,时间一过则当前线程可以执行,
​
countDown();//对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。

3.应用

我们经常会在一个接口中调用多个第三方接口,然后将结果返回,其实就可以通过CountDownLatch来实现

  public static void main(String[] args) {
        CountDownLatch count = new CountDownLatch(3);
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep((int)(Math.random()*1000));
                    System.out.println("获取接口一的数据");
                    count.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep((int)(Math.random()*1000));
                    System.out.println("获取接口二的数据");
                    count.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep((int)(Math.random()*1000));
                    System.out.println("获取接口三的数据");
                    count.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        thread2.start();
        thread3.start();
        try {
            count.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("执行成功");
    }

4.实现原理

(1)创建计数器

  public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);//创建同步队列,并设置初始计数器值
    }

(2)Sync类

可以看出该类是继承AQS的,所以CountDownLatch的实现大多都是通过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) {
            // 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;
            }
        }
    }

(3)await方法

当我们调用countDownLatch.wait()的时候,会创建一个节点,加入到AQS阻塞队列,并同时把当前线程挂起,其实就是调用共享模式下的锁获取,详情看AQS文章

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

 在Sync类重写的tryAcquireShared()方法中getState()只有等于0才会获取到锁,所以当countDownLatch待执行的任务数大于0都会堵塞该线程直到所有任务都完成

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

AQS中触发堵塞线程的源码:

private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //新建节点加入阻塞队列
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                //获得当前节点pre节点
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);//返回锁的state
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //重组双向链表,清空无效节点,挂起当前线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

(4)countDown()方法

当我们调用countDownLatch.countDown()方法的时候,会对计数器进行减1操作,AQS内部是通过释放锁的方式,对state进行减1操作,当state=0的时候证明计数器已经递减完毕,此时会将AQS阻塞队列里的节点线程全部唤醒。

public void countDown() {
        //递减锁重入次数,当state=0时唤醒所有阻塞线程
        sync.releaseShared(1);
    }
public final boolean releaseShared(int arg) {
        //递减锁的重入次数
        if (tryReleaseShared(arg)) {
            doReleaseShared();//唤醒队列所有阻塞的节点
            return true;
        }
        return false;
    }
 private void doReleaseShared() {
        //唤醒所有阻塞队列里面的线程
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {//节点是否在等待唤醒状态
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改状态为初始
                        continue;
                    unparkSuccessor(h);//成功则唤醒线程
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

到此这篇关于Java并发工具类之CountDownLatch详解的文章就介绍到这了,更多相关CountDownLatch详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • jmeter设置全局变量与正则表达式提取器过程图解

    jmeter设置全局变量与正则表达式提取器过程图解

    这篇文章主要介绍了jmeter设置全局变量与正则表达式提取器过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Java关键字volatile知识点总结

    Java关键字volatile知识点总结

    在本篇文章里小编给大家整理的是一篇关于Java关键字volatile知识点总结内容,有兴趣的朋友们可以学习参考下。
    2021-01-01
  • Springboot测试类没有bean注入问题解析

    Springboot测试类没有bean注入问题解析

    这篇文章主要介绍了Springboot测试类没有bean注入问题解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Mybatis Mapper中多参数方法不使用@param注解报错的解决

    Mybatis Mapper中多参数方法不使用@param注解报错的解决

    这篇文章主要介绍了Mybatis Mapper中多参数方法不使用@param注解报错的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教。
    2022-01-01
  • SpringBoot实现api加密的示例代码

    SpringBoot实现api加密的示例代码

    在项目中,为了保证数据的安全,我们常常会对传递的数据进行加密。本文主要介绍了SpringBoot实现api加密的示例代码,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • Java date format时间格式化操作示例

    Java date format时间格式化操作示例

    这篇文章主要介绍了Java date format时间格式化操作,结合具体实例形式分析了java针对日期时间进行格式化操作的相关实现技巧,需要的朋友可以参考下
    2017-03-03
  • 关于idea无法修改模板中jdk版本问题

    关于idea无法修改模板中jdk版本问题

    这篇文章主要介绍了关于idea无法修改模板中jdk版本问题,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • 使用SpringCache进行缓存数据库查询方式

    使用SpringCache进行缓存数据库查询方式

    这篇文章主要介绍了使用SpringCache进行缓存数据库查询方式,具有很好的参考价值,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 详解Spring中Bean的加载的方法

    详解Spring中Bean的加载的方法

    本篇文章主要介绍了Spring中Bean的加载的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-04-04
  • Java中的System类、BigInteger类和BigDecimal类详解

    Java中的System类、BigInteger类和BigDecimal类详解

    这篇文章主要介绍了Java中的System类、BigInteger类和BigDecimal类详解,arraycopy()方法,复制数组元素,比较适合底层调用,一般使用Arrays.copyOf()完成复制数组,需要的朋友可以参考下
    2023-09-09

最新评论