【JavaEE】JUC 常见的类 -- 多线程篇(8)
JUC 常见的类
- 1. Callable 接口
- 2. ReentrantLock
- 3. 原子类
- 4. 线程池
- 5. 信号量 Semaphore
- 6. CountDownLatch
1. Callable 接口
- Callable Interface 也是一种创建线程的方式
- Runnable 能表示一个任务 (run方法) – 返回 void
- Callable 也能表示一个任务(call方法) 返回一个具体的的值, 类型可以通过泛型参数来指定(Object)
代码示例: 创建线程计算 1 + 2 + 3 + … + 1000, 不使用 Callable 版本
- 创建一个类 Result , 包含一个 sum 表示最终结果, lock 表示线程同步使用的锁对象.
- main 方法中先创建 Result 实例, 然后创建一个线程 t. 在线程内部计算 1 + 2 + 3 + … + 1000.
- 主线程同时使用 wait 等待线程 t 计算结束. (注意, 如果执行到 wait 之前, 线程 t 已经计算完了, 就不必等待了).
- 当线程 t 计算完毕后, 通过 notify 唤醒主线程, 主线程再打印结果.
static class Result {public int sum = 0;public Object lock = new Object();
}
public static void main(String[] args) throws InterruptedException {Result result = new Result();Thread t = new Thread() {@Overridepublic void run() {int sum = 0;for (int i = 1; i <= 1000; i++) {sum += i;}synchronized (result.lock) {result.sum = sum;result.lock.notify();}}};t.start();synchronized (result.lock) {while (result.sum == 0) {result.lock.wait();}System.out.println(result.sum);}
}
可以看到, 上述代码需要一个辅助类 Result, 还需要使用一系列的加锁和 wait notify 操作, 代码复杂, 容易出错.
代码示例: 创建线程计算 1 + 2 + 3 + … + 1000, 使用 Callable 版本
- 创建一个匿名内部类, 实现 Callable 接口. Callable 带有泛型参数. 泛型参数表示返回值的类型.
- 重写 Callable 的 call 方法, 完成累加的过程. 直接通过返回值返回计算结果.
- 把 callable 实例使用 FutureTask 包装一下.
- 创建线程, 线程的构造方法传入 FutureTask . 此时新线程就会执行 FutureTask 内部的 Callable 的call 方法, 完成计算. 计算结果就放到了 FutureTask 对象中.
- 在主线程中调用 futureTask.get() 能够阻塞等待新线程计算完毕. 并获取到 FutureTask 中的结果.
// 使用 Callable 版本实现 1 累加到 100
public static void main(String[] args) throws ExecutionException, InterruptedException {// 1. 创建实现Callable接口的匿名内部类Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {int sum = 0;for (int i = 1; i <= 100; i++) {sum += i;}return sum;}};// 2. 创建futureTask类 -- 要来存储callable的返回值FutureTask<Integer> futureTask = new FutureTask<>(callable);Thread t = new Thread(futureTask);t.start();// 3. 调用futureTask的get方法 -- 该方法是阻塞等待的Integer result = futureTask.get();System.out.println(result);
}
- 可以看到, 使用 Callable 和 FutureTask 之后, 代码简化了很多, 也不必手动写线程同步代码了.
理解Callable
- Callable 和 Runnable 相对, 都是描述一个 “任务”. Callable 描述的是带有返回值的任务, Runnable 描述的是不带返回值的任务.
- Callable 通常需要搭配 FutureTask 来使用. FutureTask 用来保存 Callable 的返回结果. 因为Callable 往往是在另一个线程中执行的, 啥时候执行完并不确定.
- FutureTask 就可以负责这个等待结果出来的工作.
2. ReentrantLock
可重入互斥锁. 和 synchronized 定位类似, 都是用来实现互斥效果, 保证线程安全.
ReentrantLock 的用法:
- lock(): 加锁, 如果获取不到锁就死等.
- trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁.
- unlock(): 解锁

ReentrantLock 和 synchronized 的区别:
- synchronized 是一个关键字, 是 JVM 内部实现的(大概率是基于 C++ 实现). ReentrantLock 是标准库的一个类, 在 JVM 外实现的(基于 Java 实现).
- synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活, 但是也容易遗漏 unlock.
- synchronized 在申请锁失败时, 会死等. ReentrantLock 可以通过 trylock 的方式等待一段时间就放弃.
- synchronized 是非公平锁, ReentrantLock 默认是非公平锁. 可以通过构造方法传入一个 true 开启公平锁模式.

- 更强大的唤醒机制. synchronized 是通过 Object 的 wait / notify 实现等待-唤醒. 每次唤醒的是一个随机等待的线程. ReentrantLock 搭配 Condition 类实现等待-唤醒, 可以更精确控制唤醒某个指定的线程.
如何选择使用哪个锁?
- 锁竞争不激烈的时候, 使用 synchronized, 效率更高, 自动释放更方便.
- 锁竞争激烈的时候, 使用 ReentrantLock, 搭配 trylock 更灵活控制加锁的行为, 而不是死等.
- 如果需要使用公平锁, 使用 ReentrantLock.
3. 原子类
使用示例

原子类的应用场景
- 计数需求
- 播放量, 点赞量…
- 同一个视频, 有很多人都在同时的播放/点赞
- 统计效果
- 统计出现错误的请求数目 – 使用原子类, 记录出错的请求数目 – 另外写一个监控服务器, 获取线上服务器的这些错误技术, 并且以曲线图的方式绘制到页面上 – 某次发布程序之后, 发现突然这里的错误数大幅度上升, 说明你这个版本代码大概率存在 bug
- 统计收到请求的总数 (衡量服务器的压力)
- 统计每个请求的响应事件 -> 平均的响应事件 (衡量服务器的运行效率)
- 线上服务通过这些统计内容, 进行简单技术 -> 实现监控服务器
4. 线程池
- 之前的文章里已经讲过了, 请跳转: 线程池

5. 信号量 Semaphore
信号量的概念
- 信号量, 用来表示 “可用资源的个数”. 本质上就是一个计数器.

Semaphore 使用示例
public static void main(String[] args) throws InterruptedException {Semaphore semaphore = new Semaphore(4);int count = 0;// acquire方法 -- P操作 -- 计数器减一semaphore.acquire();System.out.println(count++);// release方法 -- V操作 -- 计数器加一semaphore.release();semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);semaphore.acquire();System.out.println(count++);
}
运行结果如下;

6. CountDownLatch
- 同时等待 N 个任务执行结束.
好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。
应用场景举例
- 下载一个大文件, 将大文件分成几个小的文件, 分别让多个线程来执行相应的下载任务, 当所有线程完成任务的时候, 该大文件也就下载完成了;
- CountDownLatch 就是用来等待所有线程完成任务的
- 和 join不同的是, join表示执行任务的线程退出了; CountDownLatch 只是等线程完成任务, 线程只要告知CountDownLatch 我完成任务即可, 可以不用被销毁
使用举例
public static void main(String[] args) throws InterruptedException {// 构造方法中, 指定创建几个任务.CountDownLatch countDownLatch = new CountDownLatch(10);for (int i = 0; i < 10; i++) {int id = i + 1;Thread t = new Thread(() -> {System.out.println("线程" + id + "正在工作");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("线程" + id + "完成工作");// 每个任务执行结束这里, 调用一下方法// 把 10 个线程想象成短跑比赛的 10 个运动员. countDown 就是运动员撞线了.countDownLatch.countDown();// 假设线程不退出while (true);});t.start();}// 主线程如何知道上述所有的任务都完成了呢??// 难道要在主线程中调用 10 次 join 嘛?// 万一要是任务结束, 但是线程不需要结束, join 不就也不行了嘛?// 主线程中可以使用 countDownLatch 负责等待任务结束.// a => all 等待所有任务结束. 当调用 countDown 次数 < 初始设置的次数, await 就会阻塞.countDownLatch.await();System.out.println("多个线程的所有任务都执行完毕了");}
相关文章:
【JavaEE】JUC 常见的类 -- 多线程篇(8)
JUC 常见的类 1. Callable 接口2. ReentrantLock3. 原子类4. 线程池5. 信号量 Semaphore6. CountDownLatch 1. Callable 接口 Callable Interface 也是一种创建线程的方式 Runnable 能表示一个任务 (run方法) – 返回 voidCallable 也能表示一个任务(call方法) 返回一个具体的…...
java项目运行时信息获取
大体思路如下,想要获取启动时处理器数量、jvm 相关信息,操作系统信息、运行机器信息 运行机器信息 import org.slf4j.Logger; import org.slf4j.LoggerFactory;import java.lang.invoke.MethodHandles;/*** 机器工具类*/ public abstract class ServerU…...
【LeetCode】71. 简化路径
1 问题 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 / 开头),请你将其转化为更加简洁的规范路径。 在 Unix 风格的文件系统中,一个点(.)表示当前目录本身…...
操作系统【OS】进程的控制【进程的创建、终止、阻塞、唤醒】
定义和过程 对应事件 创建 允许一个进程创建另一个进程允许子进程继承父进程所拥有的资源创建进程的过程如下: 申请一个空白的 PCB,并向 PCB 中填写一些控制和管理进程的信息,比如进程的唯一标识等;为该进程分配运行时所必需的…...
写一个简单的解释器(2) 构建标记流
确定标记类型 分为几个大类: 用户符号(类型/标识符/数字/字符串…)关键字 (流程控制和定义符)括号 (这里暂时认为 [] 属于括号)分号 上述四类标记基本囊括了 vc \texttt{vc} vc 中的所有最小单元的类型,但是因为构…...
Leetcode1833. 雪糕的最大数量
Every day a Leetcode 题目来源:1833. 雪糕的最大数量 解法1:贪心 排序 本题唯一的难点在于计数排序。 计数排序详解:C算法之计数排序 为了尽可能多的买到雪糕,我们选择从价格低的雪糕开始买,统计能够买到的雪糕…...
idea 里 没有svn选项的处理办法
总结一下没有svn选项的几种情况: 情况1:IntelliJ IDEA打开带SVN信息的项目不显示SVN信息,项目右键SVN以及图标还有Changes都不显示解决方法 在VCS菜单中有个开关,叫Enabled Version Control Integration,在打开的窗口…...
基于SpringBoot的招生管理系统
基于SpringBoot的招生管理系统的设计与实现~ 开发语言:Java数据库:MySQL技术:SpringBootMyBatisVue工具:IDEA/Ecilpse、Navicat、Maven 系统展示 主页 登录界面 管理员界面 用户界面 摘要 基于SpringBoot的招生管理系统是一款现…...
01、MySQL-------性能优化
目录 一、影响性能的相关因素存储过程: 二、sql优化1>、Mysql系统架构2>、引擎区别: 3>、索引1、什么是索引?联合主键索引理解:索引长度理解:什么是慢查询? 1)、索引理解2)…...
Flutter - APP跳转高德、百度、腾讯、谷歌地图
demo 地址: https://github.com/iotjin/jh_flutter_demo 代码不定时更新,请前往github查看最新代码 这里介绍的是不需要自己开发地图,直接通过给定的经纬度,跳转到三方地图APP调用导航的方式 一种是写的工具类,一种是通过调用三方…...
Flyway Desktop updated
Flyway Desktop updated 为比较工件序列化和反序列化添加了额外的调试日志记录。 Flyway Desktop现在将记住以前用于创建项目和匹配克隆的位置。 新的脱机许可工作流现在已在Microsoft Windows上启用。 现在,在配置目标数据库列表时,环境ID是可见的。 现…...
阿里云短信服务设置操作项目
在这里插入图片描述...
学习笔记|串口通信实战|简易串口控制器|sprintf函数|STC32G单片机视频开发教程(冲哥)|第二十一集(下):串口与PC通信
目录 3.串口通信实战实操简易的工作原理Tips:sprintf函数简介 总结课后练习 3.串口通信实战 做一个简易串口控制器。发送对应指令,让板子做相应的事情,或者传输数据(文本模式下发送,不要选择HEX)。 1.串口发送字符Ax\…...
卷积神经网络CNN学习笔记-卷积计算Conv2D函数的理解
目录 1.全连接层存在的问题2.卷积运算3.填充(padding)3.1填充(padding)的意义 4.步幅(stride)5.三维数据的卷积运算6.结合方块思考7.批处理8.Conv2D函数解析9.conv2d代码9.1 stride19.2 stride2 参考文章 1.全连接层存在的问题 在全连接层中,相邻层的神经元全部连接…...
收藏,安装报错信息汇总,MacOS上安装Adobe等软件/插件报错问题解决合集
打开允许“允许任何来源” 如何打开允许任何来源?在 Finder 菜单栏选择 【前往】 – 【实用工具 】,找到【终端】程序,双击打开,在终端窗口中输入:sudo spctl --master-disable 输入代码后,按【return 回车…...
Qt 报错:munmap_chunk(): invalid pointer解决方案
问题 在用Qt写程序的时候。报了munmap_chunk(): invalid pointer这个错误消息。 造成原因 在 Qt 程序中,这种错误可能出现在多种情况下,以下几点是容易造成此问题的原因: 函数未实现返回值:函数有返回值,但函数体中…...
【Java题】实现继承和多态的例子
一:题目 1.员工类Employee: (1)私有成员变量:姓名,年龄,工资 (2)提供无参,有参构造 (3)成员方法:work()方法——员工工作 …...
‘conda‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。
原因:环境变量没有正确添加解决:我的电脑—右键属性—高级系统设置—环境变量—系统变量—Path—双击进入—新建,去安装Anaconda的目录下,找到Library中的bin,将此时的路径粘贴到此处...
C1N短网址 - 是如何做到行业领先的
今天从技术角度来聊下短网址的一些事情,市面上的短网址发展基本上经历了几个阶段。 短网址发展的几个阶段: 第一阶段:网址缩短,很纯粹的功能,各个大小公司都在做,门槛很低。典型代表:百度短网…...
【UE5】引入C++插件Plugins不在UE里出现
原因 未编译过C 原项目为蓝图项目,或者虽然为C项目,但并为编译过C. 解决 创建一个C脚本,让编辑器重启重新编译一遍。 如还不行,则打开Plugins插件面板,创建一个空的新的插件,再让引擎自动重启重新编译…...
PlatformIO的platformio.ini文件还能这么玩?一个项目搞定STM32多下载器与条件编译
PlatformIO高阶技巧:platformio.ini的多下载器管理与条件编译实战 当你已经能够用PlatformIO完成基础的STM32开发后,是否曾想过如何让项目配置更加智能和高效?platformio.ini这个看似简单的配置文件,实际上隐藏着令人惊喜的强大功…...
WindowResizer终极指南:如何强制调整任意Windows窗口大小
WindowResizer终极指南:如何强制调整任意Windows窗口大小 【免费下载链接】WindowResizer 一个可以强制调整应用程序窗口大小的工具 项目地址: https://gitcode.com/gh_mirrors/wi/WindowResizer 你是否曾遇到过那些"顽固"的Windows应用程序窗口&a…...
QT6.1.2安装后,第一件事该做什么?从环境变量配置到创建第一个Hello World项目
QT6.1.2安装后环境配置与首个项目实战指南 当你完成QT6.1.2的安装,面对Qt Creator的空白界面时,可能会感到一丝茫然。别担心,这是每个开发者都会经历的阶段。本文将带你完成从环境验证到第一个Hello World项目的完整流程,让你快速…...
保研面试避坑指南:为什么我刷了两个月408和OJ,导师却只问我的大创项目?
保研面试的认知突围:为什么导师更关注你的项目而非专业课? 每年保研季,总有一批计算机专业的学生陷入同样的困境——他们刷遍了408四门专业课的习题集,在LeetCode上解决了上百道算法题,却在面试现场被导师连续追问一个…...
Mac效率提升:一键neofetch查系统信息,再也不用点‘关于本机’了(含.zshrc配置详解)
Mac效率革命:用neofetch打造终极系统信息仪表盘 每次需要查看Mac的硬件配置或系统版本时,你是不是还在点击左上角苹果图标→"关于本机"?作为一名整天与终端打交道的开发者,我发现了一个能节省大量时间的秘密武器——neo…...
从直播流到本地文件:TS格式在HLS/HTTP Live Streaming中的核心作用与实战抓包分析
TS格式在HLS流媒体中的技术解析与实战抓包指南 当你在手机上观看一场体育赛事直播时,背后是数以千计的.ts切片文件通过HTTP协议源源不断地传输到你的设备。这种看似简单的技术实现,实际上蕴含着流媒体领域最精妙的设计思想。作为HLS(HTTP Liv…...
保姆级避坑指南:在Ubuntu 20.04上从源码编译ORB-SLAM3(含Pangolin、OpenCV版本冲突解决)
保姆级避坑指南:在Ubuntu 20.04上从源码编译ORB-SLAM3(含Pangolin、OpenCV版本冲突解决) 视觉SLAM领域的研究者和开发者们,想必对ORB-SLAM3这个开源的视觉惯性SLAM系统都不陌生。作为ORB-SLAM系列的第三代产品,它在精度…...
3大核心功能+5步快速上手:WeReader让微信读书笔记效率翻倍
3大核心功能5步快速上手:WeReader让微信读书笔记效率翻倍 【免费下载链接】wereader 一个浏览器扩展:主要用于微信读书做笔记,对常使用 Markdown 做笔记的读者比较有帮助。 项目地址: https://gitcode.com/gh_mirrors/wer/wereader 你…...
Vue3 + AntV X6 实战:从零封装一个可拖拽连线的关系图组件(附完整代码)
Vue3 AntV X6 实战:封装高可定制的关系图组件 在复杂业务系统中,可视化关系图谱正成为不可或缺的交互界面。本文将带你从零构建一个生产级的关系图组件,基于Vue3和AntV X6实现节点拖拽、动态连线、自动布局等核心功能,最终封装成…...
EF Core 10向量搜索上线72小时后必须做的6项健康检查:从ANN精度衰减预警到HNSW图分裂检测(含Prometheus+Grafana监控模板)
第一章:EF Core 10向量搜索扩展的生产就绪性定义生产就绪性并非仅指功能可用,而是涵盖稳定性、可观测性、可维护性、安全边界与性能可预测性五个核心维度。EF Core 10 向量搜索扩展(Microsoft.EntityFrameworkCore.VectorSearch)虽…...
