理解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工作
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CountDownLatchJava中的一个同步工具类,它可以让一个或多个线程等待其他线程完成操作后再执行。CountDownLatch使用方式如下: 1. 创建CountDownLatch对象,指定计数器的初始值,即需要等待的线程数。 2. 在需要等待的线程中调用CountDownLatch的await()方法,使线程进入等待状态。 3. 在其他线程完成操作后,调用CountDownLatchcountDown()方法,使计数器减1。 4. 当计数器减为0时,所有等待的线程都会被唤醒,继续执行。 下面是一个简单的示例代码: ``` import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { int threadCount = 5; CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { new Thread(() -> { // 线程执行操作 System.out.println(Thread.currentThread().getName() + " 执行操作"); // 操作完成后计数器减1 countDownLatch.countDown(); }).start(); } // 等待所有线程执行完毕 countDownLatch.await(); System.out.println("所有线程执行完毕"); } } ``` 在上面的示例中,我们创建了一个CountDownLatch对象,并指定计数器的初始值为5。然后创建了5个线程,每个线程执行完操作后都会调用countDown()方法,使计数器减1。最后在主线程中调用await()方法,等待所有线程执行完毕。当计数器减为0时,所有线程都会被唤醒,主线程继续执行输出"所有线程执行完毕"。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值