文章目录

  • 1. 什么是闭锁
  • 2. 闭锁的应用场景
  • 3. 闭锁的实现
  • 4. CountDownLatch原理
  • 5. 使用案例
    • 5.1 入门案例
    • 5.2 复杂案例

1. 什么是闭锁

闭锁(latch)是一种 Synchronizer,他可以延迟线程的进度直到线程到达终止状态。

一个闭锁工作起来就像一道大门:直到闭锁达到终点状态之前,门一直是关闭的,没有线程通过,在终点状态到来的时候,门开了,允许所有线程都通过。一旦闭锁到达了终点状态,他就不能够在改变状态了,所以它会永远保持敞开的状态。

2. 闭锁的应用场景

  • 确保一个计算不会执行,直到它需要的资源被初始化。

  • 确保一个服务不会开始,直到它依赖的服务都已经开始。

  • 等待直到活动的所有部分都为继续处理做好准备。比如王者荣耀需要等待所有玩家准备才能开始

    Jmeter模拟高并发也是这个场景,如果我想测试10个线程同时工作对cpu的影响,那么如果线程执行的快慢程度不一样,可能第10个线程刚创建,第一个线程就执行结束了,此时就只有9个线程在同时执行,和我预期不一致,此时,就可以用CountDownLatch 控制,在下文5.2复杂例子,就有这样的例子!

3. 闭锁的实现

CountDownLatch是一个同步辅助类,存在于java.util.concurrent包下,灵活的实现了闭锁,它允许一个或多个线程等待一个事件集的发生。

CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数的值就会减1。当计数器值到达0时,它所表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。

4. CountDownLatch原理

CountDownLatch构造函数:

CountDownLatch(int count);

构造器中计数值(count)就是闭锁需要等待的线程数量,这个值只能被设置一次。

CountDownLatch类的方法:

  • void await():使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。

  • boolean await(long timeout, TimeUnit unit):使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。

    其实就是 void await(),额外多了一个超时时间,避免某个因素导致锁长久未归零,程序可以提前做出处理。

  • void countDown():递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

  • long getCount():返回当前计数。

  • String toString():返回标识此锁存器及其状态的字符串。

常规用法是这样:线程A调用锁B的await()方法后,线程A会等待锁B归零,期间线程A会一直阻塞,如果锁B一直未归零,线程A也会一直阻塞下去;如果用await(timeout)的话,超时后,会自行唤醒,继续完成后续流程。

同样的,如果其他的N个线程也调用锁B的await()方法后象,这些线程会和线程A一样,都会阻塞,直至锁B归零。

5. 使用案例

5.1 入门案例

我们前面介绍了CountDownLatch 的使用场景,那就是等待资源就绪。

假设你是一个厨师,你有2个助手,你要做一道荤菜,那么需要肉和调味品,你可以吩咐2个助手分别去买肉和买盐,等到2个助手都回来后,你才能点火做饭,在此期间,你就一直在等待。如果某个助手先到了,你也不能做任何事情,继续等待另一个的到来。

买肉任务:

package com.test;import java.util.concurrent.CountDownLatch;/*** 买肉任务*/
public class BuyMeatTask implements Runnable {private CountDownLatch countDownLatch;public BuyMeatTask(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}public void run() {try {System.out.println("出门去买肉了");Thread.sleep(2000);System.out.println("肉买来了!");} catch (InterruptedException e) {e.printStackTrace();} finally {if (countDownLatch != null)countDownLatch.countDown();}}}

买盐的任务:

package com.test;import java.util.concurrent.CountDownLatch;/*** 买盐的任务*/
public class BuySaltTask implements Runnable {private CountDownLatch countDownLatch;public BuySaltTask(CountDownLatch countDownLatch) {this.countDownLatch = countDownLatch;}public void run() {try {System.out.println("出门买盐了");Thread.sleep(5000);System.out.println("盐买回来了!");} catch (InterruptedException e) {e.printStackTrace();} finally {if (countDownLatch != null)countDownLatch.countDown();}}
}

执行类:

package com.test;import java.util.concurrent.CountDownLatch;/*** 我是厨师,等待配菜和调味品*/
public class CountDownLaunchRunner {static int sub = 0;static Object object = new Object();public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(2);new Thread(new BuyMeatTask(countDownLatch)).start();new Thread(new BuySaltTask(countDownLatch)).start();// 等待线程池中的2个任务执行完毕,否则一直等待countDownLatch.await();System.out.println("准备点火炒菜");}}

执行结果:

出门去买肉了
出门买盐了
肉买来了!
盐买回来了!
准备点火炒菜

当子线程调用countDownLatch.countDown()满足为0时,会发通知消息,唤醒阻塞的主线程:

5.2 复杂案例

我们使用书上原文的例子,我们想统计所有子线程的运行时间,由于子线程运行时间有长有短,比如子线程A花费10s,子线程B花费2S,那么结果应该是最长的那个,即10s,因此,我们希望在最后一个线程(耗时最长的那个)执行完毕后,由主线程统计时间。

package net.jcip.examples;import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;public class TestHarness {public long timeTasks(int nThreads, final Runnable task) throws InterruptedException {// 开始锁,当其值变为0时,表示等待的线程可以运行final CountDownLatch startGate = new CountDownLatch(1);// 结束锁,当其值变为0时,表示最后一个线程也执行完了,也就是说所有的线程都执行完了final CountDownLatch endGate = new CountDownLatch(nThreads);for (int i = 0; i < nThreads; i++) {// 模拟不同线程的间隔一定的时间才创建好TimeUnit.SECONDS.sleep(1);Thread t = new Thread() {public void run() {try {System.out.println(new Date() + " " + Thread.currentThread().getName()+ " started");startGate.await(); // <注1>每个子线程立即进入等待,直至开始锁释放,才一起运行try {task.run();} finally {endGate.countDown(); // <注2>每个子线程运行结束,通知结束锁计数器减一}} catch (InterruptedException ignored) {}}};t.start(); // 启动子线程}long start = System.nanoTime(); // 开始的时间// 为了让结果更直观,主线程增加休眠,避免间隔太短,瞬时打印日志IO顺序不符合预期// 确保所有的线程都达到阻塞点,再去唤醒它们Thread.sleep(1000);startGate.countDown(); // 开始锁减一,计数器归零,通知所有的子线程(卡在<注1>)处的)可以执行了endGate.await(); // 主线程进入等待,直至最后一个线程运行结束(<注2>处发出通知),才会被唤醒,继续执行long end = System.nanoTime();return end - start; // 统计时间}public static void main(String[] args) throws InterruptedException {Runnable task = new Runnable() {@Overridepublic void run() {// 模拟线程耗时不同int counter = new Random().nextInt(100);System.out.println(new Date() + " " + Thread.currentThread().getName()+ " will run " + counter + " times");for (int i = 0; i < counter; i++) {try {Thread.sleep(100); // 模拟耗时处理} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(new Date() + " " + Thread.currentThread().getName()+ " finished");}};TestHarness test = new TestHarness();long time = test.timeTasks(3, task);System.out.println("total time is : " + time);}
}

执行结果,每次内容随机,但存在特定规律:

Fri Feb 12 17:03:05 CST 2021 Thread-0 started
Fri Feb 12 17:03:06 CST 2021 Thread-1 started
Fri Feb 12 17:03:07 CST 2021 Thread-2 started
Fri Feb 12 17:03:08 CST 2021 Thread-2 will run 88 times
Fri Feb 12 17:03:08 CST 2021 Thread-0 will run 71 times
Fri Feb 12 17:03:08 CST 2021 Thread-1 will run 39 times
Fri Feb 12 17:03:12 CST 2021 Thread-1 finished
Fri Feb 12 17:03:15 CST 2021 Thread-0 finished
Fri Feb 12 17:03:17 CST 2021 Thread-2 finished
total time is : 9853746100

分析:通过结果的注释,基本上就能看到规则了,主线程确实是在所有子线程结束后,才统计的运行时间。

我们之前列举了CountDownLatch 的3个应用场景,这个例子里面有2个锁,覆盖了其中的2种:

  • 等待直到活动的所有部分都为继续处理做好准备
    startGate锁保证所有的子线程都准备就绪后,才开始;
  • 确保一个计算不会执行,直到它需要的资源被初始化
    endGate锁保证主线程依赖的所有子线程都完成后,才开始 ;对于主线程来说,子线程完成就是它依赖资源。

参考:【Java并发编程三】闭锁

【Java并发编程实战】 5.5.1章节 闭锁 CountDownLatch 实现相关推荐

  1. 前置条件,不变性条件,后置条件 --《java并发编程实战》

    阅读<java并发编程实战>4.1.1章 收集同步需求时, 反复出现了"不变性条件","不可变条件","后验条件",令我一头雾水 ...

  2. 【极客时间】《Java并发编程实战》学习笔记

    目录: 开篇词 | 你为什么需要学习并发编程? 内容来源:开篇词 | 你为什么需要学习并发编程?-极客时间 例如,Java 里 synchronized.wait()/notify() 相关的知识很琐 ...

  3. Java并发编程实战笔记2:对象的组合

    设计线程安全的类 在设计现车让安全类的过程之中,需要包含以下三步: 找出构成对象状态的所有变量 找出约束状态变量的不变性条件 建立对象状态的并发访问策略 实例封闭 通过封闭机制与合适的加锁策略结合起来 ...

  4. aqs clh java_【Java并发编程实战】—– AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形. 其主要从双方面进行了改造:节点的结构与节点等待机制.在结构上引入了 ...

  5. java 多线程缓存_[Java教程]【JAVA并发编程实战】12、使用condition实现多线程下的有界缓存先进先出队列...

    [Java教程][JAVA并发编程实战]12.使用condition实现多线程下的有界缓存先进先出队列 0 2016-11-29 17:00:10 package cn.study.concurren ...

  6. Java并发编程实战————恢复中断

    中断是一种协作机制,一个线程不能强制其他线程停止正在执行的操作而去执行其他操作. 什么是中断状态? 线程类有一个描述自身是否被中断了的boolean类型的状态,可以通过调用 .isInterrupte ...

  7. Java并发编程实战————Executor框架与任务执行

    引言 本篇博客介绍通过"执行任务"的机制来设计应用程序时需要掌握的一些知识.所有的内容均提炼自<Java并发编程实战>中第六章的内容. 大多数并发应用程序都是围绕&qu ...

  8. Java并发编程实战————Semaphore信号量的使用浅析

    引言 本篇博客讲解<Java并发编程实战>中的同步工具类:信号量 的使用和理解. 从概念.含义入手,突出重点,配以代码实例及讲解,并以生活中的案例做类比加强记忆. 什么是信号量 Java中 ...

  9. Java并发编程实战_不愧是领军人物!这种等级的“Java并发编程宝典”谁能撰写?...

    前言 大家都知道并发编程技术就是在同一个处理器上同时的去处理多个任务,充分的利用到处理器的每个核心,最大化的发挥处理器的峰值性能,这样就可以避免我们因为性能而产生的一些问题. 大厂的核心负载肯定是非常 ...

  10. java并发编程实战学习(3)--基础构建模块

    转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put ...

最新文章

  1. python函数结构_Python函数基础
  2. CF 976F 递增容量最大流
  3. python 猜数字大小
  4. 输入框不可以输入中文
  5. 【博客园2018总结帖】21天,在Github上获取 6300 star
  6. 使用scriptreference使你的页面加载更快
  7. 苹果发布2019年上半年透明度报告,收到数万条政府请求
  8. 系统运维哪些事儿之正常状态
  9. 三级计算机信息安全基础知识
  10. X8AIP 驱动程序
  11. CMMI-V2.0真题模拟(1)
  12. 第二章 02 天牛质感
  13. Learning to Reweight Examples for Robust Deep Learning
  14. 全国建筑模架业内人士齐聚联筑赚 携手并进同发展
  15. 房产管理系统系统与数据中心对接要遵循的标准和规范
  16. 五金机电行业供应商智慧管理平台解决方案:优化供应链管理,带动企业业绩增长
  17. CentOS7.9下安装Oracle19c
  18. 数据库系统概论(第十章数据库恢复技术)
  19. 记一次华为RH2288-3V _X86架构服务器的操作系统安装
  20. VS中编写Qt项目时,提取ts文件出现中文乱码的解决方法(填坑)

热门文章

  1. 详解python使用金山词霸的翻译功能(调试工具断点的使用)
  2. 如何创建一个Facebook群组
  3. 不在同一局域网的两台电脑如何通过ssh连接
  4. 现代数学观,何处寻?
  5. Catalan 数之Python演示
  6. 关于GPS坐标转换(一)
  7. smart-link/monitor-link
  8. 低成本2.4G SOC(NYA054E)灯控遥控芯片方案-CI2454/CI2451
  9. amazon mechanical turk介绍
  10. java8的时期和时间