文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

怎么在Java中使用CountDownLatch实现并发编程

2023-06-14 18:24

关注

本篇文章为大家展示了怎么在Java中使用CountDownLatch实现并发编程,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。

Java是什么

Java是一门面向对象编程语言,可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统应用程序。

一、前言

CountDownLatch维护了一个计数器(还是是state字段),调用countDown方法会将计数器减1,调用await方法会阻塞线程直到计数器变为0。可以用于实现一个线程等待所有子线程任务完成之后再继续执行的逻辑,也可以实现类似简易CyclicBarrier的功能,达到让多个线程等待同时开始执行某一段逻辑目的。

二、使用

......CountDownLatch cdl = new CountDownLatch(10);ExecutorService es = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {es.execute(() -> {//do somethingcdl.countDown();});}cdl.await();......
......        CountDownLatch cdl = new CountDownLatch(1);        ExecutorService es = Executors.newFixedThreadPool(10);        for (int i = 0; i < 10; i++) {            es.execute(() -> {                cdl.await();                //do something            });        }        Thread.sleep(10000L);        cdl.countDown();        ......

三、源码分析

CountDownLatch的结构和ReentrantLock、Semaphore的结构类似,也是使用的内部类Sync继承AQS的方式,并且重写了tryAcquireShared和tryReleaseShared方法。

还是首先来看构造函数:

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

需要传入一个大于0的count,代表CountDownLatch计数器的初始值,通过Sync的构造函数最终赋值给父类AQS的state字段。可一个看到这个state字段用法多多,在ReentrantLock中使用0和1来标识锁的状态,Semaphore中用来标识信号量,此处又用来表示计数器。

CountDownLatch要通过await方法完成阻塞,先来看看这个方法是如何实现的:

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

调用的是sync的acquireSharedInterruptibly方法,该方法定义在AQS中,Semaphore也调用的这个方法:

public final void acquireSharedInterruptibly(int arg)            throws InterruptedException {        if (Thread.interrupted())            throw new InterruptedException();        if (tryAcquireShared(arg) < 0)            doAcquireSharedInterruptibly(arg);    }

这个方法的逻辑前面在解析SemaPhore的时候细说过了,这里不再赘述,主要就是两个方法的调用,先通过tryAcquireShared方法尝试获取"许可",返回值代表此次获取后的剩余量,如果大于等于0表示获取成功,否则表示失败。如果失败,那么就会进入doAcquireSharedInterruptibly方法执行入队阻塞的逻辑。这里我们主要到CountDownLatch中看看tryAcquireShared方法的实现:

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

和Semaphore的实现中每次将state减去requires不同,这里直接判断state是否为0,如果为0那么返回1,表示获取"许可"成功;如果不为0,表示失败,则需要入队阻塞。从这个tryAcquireShared方法就能看出CountDownLatch的逻辑了:等到state变为了0,那么所有线程都能获取运行许可。

那么我们接下来来到countDown方法:

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

调用的是sync的releaseShared方法,该方法定义在父类AQS中,Semaphore使用的也是这个方法:

public final boolean releaseShared(int arg) {        if (tryReleaseShared(arg)) {        //当state从非            doReleaseShared();            return true;        }        return false;    }

前面提到了CountDownLatch也重写了tryReleaseShared方法:

protected boolean tryReleaseShared(int releases) {            // Decrement count; signal when transition to zero            for (;;) {                int c = getState();                if (c == 0)                //如果state等于0了直接返回false                //保证在并发情况下,最多只会有一个线程返回true                //也包括调用countDown的次数超过state的初始值                    return false;                int nextc = c-1;                if (compareAndSetState(c, nextc))                //如果返回true,表示state从非0变为了0                //那么后续需要唤醒阻塞线程                    return nextc == 0;            }        }

Semaphore在释放信号量的时候,是将获取的许可归还到state中,但是CountDownLatch没有获取许可的逻辑(获取许可的时候是判断state是否等于0),所以在countDown的时候没有释放的逻辑,就是将state减1,然后根据state减1之后的值是否为0判断release是否成功,如果state本来大于0,经过减1之后变为了0,那么返回true。tryReleaseShared方法的返回值决定了后续需不需要调用doReleaseShared方法唤醒阻塞线程。

这里有个逻辑:如果state已经为0,那么返回false。这个主要应对两种情况:

可以看到CountDownLatch没有锁的概念,countDown方法可以被一个线程重复调用,只需要对state做reduce操作,而不用关心是谁做的reduce。如果tryReleaseShared返回true,那么表示需要在后面进入doReleaseShared方法,该方法和Semaphore中调用的方法是同一个,主要是唤醒阻塞线程或者设置PROPAGAGE状态,这里也不再赘述~

阻塞线程被唤醒之后,会在doAcquireSharedInterruptibly方法中继续循环,虽然和Semaphore调用的是同样的方法,但是这里有不一样的地方,所以还是提一句。我们首先回到doAcquireSharedInterruptibly方法:

private void doAcquireSharedInterruptibly(int arg)        throws InterruptedException {        final Node node = addWaiter(Node.SHARED);        boolean failed = true;        try {            for (;;) {                final Node p = node.predecessor();                if (p == head) {                //如果head.next被unpark唤醒,说明此时state==0                //那么tryAcquireShared会返回1                    int r = tryAcquireShared(arg);                    //r==1                    if (r >= 0) {                    //node节点被唤醒后,还会继续唤醒node.next                    //这样依次传递,因为在这里的r一定为1                        setHeadAndPropagate(node, r);                        p.next = null; // help GC                        failed = false;                        return;                    }                }                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    throw new InterruptedException();            }        } finally {            if (failed)                cancelAcquire(node);        }    }

当head.next线程被unpark唤醒后,会进入tryAcquireShared方法判断,由于此时state已经为0(只有当state变为0时,才会unpark唤醒线程),而前面提到了在CountDownLatch重写的tryAcquireShared中,如果state==0,那么会返回1,所以会进入setHeadAndPropagate方法:

private void setHeadAndPropagate(Node node, int propagate) {        Node h = head; // Record old head for check below        setHead(node);        if (propagate > 0 || h == null || h.waitStatus < 0 ||            (h = head) == null || h.waitStatus < 0) {            Node s = node.next;            if (s == null || s.isShared())                doReleaseShared();        }    }

该方法在Semaphore中详细介绍过,这里我们就站在CountDownLatch的角度来看看。其实很简单了,注意此时该方法的propagate参数值是1,那么就会进入到下面的if逻辑里,继续唤醒下一个node。当下一个node对应的线程被唤醒后,同样会进入setHeadAndPropagate方法,propagage同样为1,那么继续唤醒下一个node,就这样依次将整个CLH队列的节点都唤醒。

上述内容就是怎么在Java中使用CountDownLatch实现并发编程,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯