【并发编程】 --- CountDownLatch原理简介 + 使用方法


源码地址:https://github.com/nieandsun/concurrent-study.git

其实我自认为CountDownLatch类是进入AQS世界一个非常好非常好的通道,因此写完这篇文章,我打算再写一篇关于CountDownLatch源码的文章。


1 原理简介

CountDownLatch原理可以用下图进行表示:
在这里插入图片描述

这里注意一下: 每个线程都可以调用countDown()1次,或多次 —> 并非每个线程都只能调用1次

其实这个CountDownLatch和Thread的join方法有点类似,下图是我在《【并发编程】— Thread类中的join方法》一文中画的join的原理图,有兴趣的可以对比一下。

在这里插入图片描述


2 具体使用方法


2.1 demo1 — await不传入时间,保证当前线程的其他操作在最后执行

  • code
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;

@Slf4j
public class CountDownLatchDemo1 {
    private final static int threadCount = 200;

    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            new Thread(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    log.error("exception:", e);
                } finally { //放在finally块里保证线程无论怎样都会执行countDown方法
                    countDownLatch.countDown();
                }
            }).start();
        }
        countDownLatch.await();

        //可以想到finish肯定会在上面的线程都运行完才执行
        log.info("finish");

    }

    private static void test(int i) throws InterruptedException {
        Thread.sleep(100);
        log.info("threadNum:{}", i);
        Thread.sleep(100);
    }
}
  • 测试结果

在这里插入图片描述


2.2 demo2 — await传入时间t,当前线程等其他线程时间t后就运行其他操作

  • code
package com.nrsc.ch2.juctools;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Slf4j
public class CountDownLatchDemo2 {
    private final static int threadCount = 200;

    public static void main(String[] args) throws InterruptedException {
        
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            new Thread(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    log.error("exception:", e);
                } finally {
                    countDownLatch.countDown();
                }
            }).start();
        }
        //等其他线程11MILLISECONDS后,放行当前线程await后的其他操作
        countDownLatch.await(11, TimeUnit.MILLISECONDS);
        log.info("finish");

    }

    private static void test(int i) throws InterruptedException {
        Thread.sleep(10);
        log.info("threadNum:{}", i);
    }
}

  • 测试结果

在这里插入图片描述
注意: 我上面的运行结果并不是唯一的,其实finish在最后输出也是有可能的。这里我让其他线程睡了10MILLISECONDS,而主线程会等11MILLISECONDS,其他线程是有可能都在11MILLISECONDS之内运行完的。


2.3 发令枪

相信很多人都听说过,CountDownLatch可以做类似发令枪的行为,我这里也实现了一个demo,代码如下:
要准确理解它为什么被比喻成发令枪,一定要多注意一下我代码中的注释,和文末测试结果图中的注释。

package com.nrsc.ch2.juctools;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

@Slf4j
public class CountDownLatchDemo3 {

    /***
     * 继承了Callable的内部类
     */
    private static class Runner implements Callable<Integer> {
        private CountDownLatch judge;//裁判
        private CountDownLatch runner;//跑步者

        public Runner(CountDownLatch begin, CountDownLatch end) {
            super();
            this.judge = begin;
            this.runner = end;
        }

        @Override
        public Integer call() throws Exception {
            int score = new Random().nextInt(10);

            //------------------------------------------------------------
            //judge不进行countDown,所有的线程都会在这里阻塞住
            //------------------------------------------------------------
            log.info("线程-{}准备就绪", Thread.currentThread().getName());
            judge.await();

            log.info("线程-{}开始跑步", Thread.currentThread().getName());
            //进行跑步
            TimeUnit.MILLISECONDS.sleep(score);//跑步需要花的时间
            //运动员跑步完成
            runner.countDown();
            return score; //将执行时间返回
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final int runnerNum = 8;
        CountDownLatch judge = new CountDownLatch(1);//裁判
        CountDownLatch runner = new CountDownLatch(runnerNum);//运动员

        //新建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(runnerNum);
        List<Future<Integer>> list = new ArrayList<>();
        for (int i = 0; i < runnerNum; i++) {
            //每个线程都开始运行了,但是都会运行到 judge.await();那里停住,等待judge的countDown方法
            Future<Integer> submit = executorService.submit(new Runner(judge, runner));
            list.add(submit);
        }

        /***
         * 注意,我故意把关闭线程池的操作放在这里了,仍然可以获取到想要的结果
         * 这是因为线程池并不会立即关闭,而是将任务执行完才会关闭
         */
        executorService.shutdown();


        //枪声响起
        judge.countDown(); //预备,开始,跑!!!!

        //等待所有的跑步者跑完
        runner.await();

        log.info("跑步结果:");
        //所有跑步者的跑步结果
        for (Future<Integer> future : list) {
            try {
                Integer res = future.get();
                System.out.print(res + " ");
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 测试结果

在这里插入图片描述


END

发布了217 篇原创文章 · 获赞 288 · 访问量 50万+

猜你喜欢

转载自blog.csdn.net/nrsc272420199/article/details/105058702
今日推荐