理解CountDownLatch(闭锁)的几种使用方式

1.原理

CountDownLatch是Spring框架中原生的一个同步辅助类,它允许一个或多个线程一直等待直到其他线程执行完毕才开始执行。
计数器通过使用锁(共享锁、排它锁)实现。

并行的过程概述,被加锁的往往是一个很长的方法,方法里每一个唯一资源(单例对象,final对象)都是一个wait点,每次只能让一个跑,这个跑过去后会继续跑几步,然后断点就会回到下一个,即新的线程就开始跑了

2.使用概述

用给定的计数初始化CountDownLatch,其含义是要被等待执行完的线程个数。
每次执行调用CountDown(),计数减1
主程序执行到await()函数会阻塞等待线程的执行,直到计数为0
3.使用场景概述
确保某个计算在其需要的所有资源都被初始化之后才继续执行;
确保某个服务在其依赖的所有其他服务都已经启动之后才启动;
等待直到某个操作所有参与者都准备就绪再继续执行。
(事实上,这里的使用场景我一个都不理解–我的理解是线程所需要的资源在线程之间共享,为了避免阻塞,所以通过常见的技巧来避免:当资源只是苹果时,我们可以单独对苹果加锁来实现,但资源是无数个实例对象时,加锁显然就要更有技巧了)
4.实例分析
4.1await–countDown
线程的开启

1.
Runnable run = new Runnable() {
                @Override
                public void run() {
                    try {
                        begin.await();
                        Thread.sleep((long)(Math.random() * 10000));
                        System.out.println("No." + NO + " arrived");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        end.countDown();
                    }
                }
2.
statementExecutorPool.execute(() -> {
				} catch (Exception ex) {
                    e[0] = ex;
                    log.error("customer monthly statement [{}] error", businessDate, ex);
                } finally {
                    latch.countDown();
                }
            });

线程之间的控制

CountDownLatch latch = new CountDownLatch(wrapper.getAccounts().size());
每跑完一次,计数器一定要减1
latch.countDown();
全部跑完的时候,一定要统一去等待,等到所有线程全部执行完,这样才能做事务控制(回滚)。
latch.await();

另外一个--两个计数器对比的案例
		final CountDownLatch begin = new CountDownLatch(1);
        final CountDownLatch end = new CountDownLatch(RUNNER_COUNT);
        final ExecutorService exec = Executors.newFixedThreadPool(10);
        try {
                        begin.await();
                        Thread.sleep((long)(Math.random() * 10000));
                        System.out.println("No." + NO + " arrived");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        end.countDown();
                    }
        System.out.println("Game Start ...");
        begin.countDown();
        end.await();
        System.out.println("Game Over.");
        
输出:
Game Start ...
No.6 arrived
No.4 arrived
No.10 arrived
No.3 arrived
No.9 arrived
No.5 arrived
No.8 arrived
No.7 arrived
No.1 arrived
No.2 arrived
Game Over.

我有点不是很看得懂这里的两个计数器有什么配合?
不是任何一个没有countdown都不会去输出结果吗?还是啥?

4.2join
几个先跑,跑完才能一个特殊的

运行的代码倒是很简单,就是2个线程先跑,然后await,再跑后面一个
但线程的设计略微麻烦一点,但认真思考了一下,好像只是把内部类封装到了另外一个实体类上去了,做到了代码的重用,
但实际上就是上面这个案例复制两份,前面的要用await,后面的不需要用。
public class WorkerCount extends Thread {
    private String name;
    private long time;
    private CountDownLatch countDownLatch;

    public WorkerCount(String name, long time, CountDownLatch countDownLatch) {
        this.name = name;
        this.time = time;
        this.countDownLatch = countDownLatch;
    }

join方法

join方法理解起来更加简单,几乎可以理解为直接对当前线程进行操作
public class Worker extends Thread{
    private String name;
    private long time;

    public Worker(String name, long time) {
        this.name = name;
        this.time = time;
    }
操作
Worker worker2 = new Worker("lilei-2", (long)(Math.random() * 10000));
        worker0.start();
        worker1.start();

        worker0.join();
        worker1.join();
        System.out.println("准备工作就绪");

        worker2.start();
        Thread.sleep(10000);
可以很明显看出,这里的join实际上就是在让当前线程”等待“,在latch里使用的是await,操作的是计数器

4.2.2join和latch的差别

可以看得出来join实际上是线程外部的一种操作,所以没办法做到精细化控制
在以下案例中,join实现不了。
流水线上有3个worker: worker1、worker2、worker3,只有当worker1和worker2两者的阶段一都执行完后才可以执行worker3
每一个work的工作并不是一个整体--包含了阶段123。。。
线程在执行到join后就不能再次开始吗?不能,因为并没停止,谈不上开始。

latch的写法

@Override
    public void run() {
        try {
            System.out.println(name + "开始阶段1工作");
            Thread.sleep(time);
            System.out.println(name + "阶段1完成, 耗时:"+ time);
            countDownLatch.countDown();

            System.out.println(name + "开始阶段2工作");
            Thread.sleep(time);
            System.out.println(name + "阶段2完成, 耗时:"+ time);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
运行
worker0.start();
        worker1.start();
        countDownLatch.await();
        System.out.println("准备工作就绪");

        WorkerCount2 worker2 = new WorkerCount2("lilei-2", (long)(Math.random() * 10000), countDownLatch);
        worker2.start();
输出
lilei-0开始阶段1工作
lilei-1开始阶段1工作
lilei-0阶段1完成, 耗时:3938
lilei-0开始阶段2工作
lilei-1阶段1完成, 耗时:6259
lilei-1开始阶段2工作
准备工作就绪
lilei-2开始阶段1工作
lilei-0阶段2完成, 耗时:3938
lilei-1阶段2完成, 耗时:6259
lilei-2阶段1完成, 耗时:7775
lilei-2开始阶段2工作
发布了29 篇原创文章 · 获赞 0 · 访问量 632

猜你喜欢

转载自blog.csdn.net/weixin_43343786/article/details/105666392