java并发-如何保证线程按照顺序执行?
【readme】
- 使用只有单个线程的线程池(最简单)
- Thread.join()
- 可重入锁 ReentrantLock + Condition 条件变量(多个) ; 原理如下:
- 任务1执行前在锁1上阻塞;执行完成后在锁2上唤醒;
- 任务2执行前在锁2上阻塞,执行完成后在锁3上唤醒;
- 任务n执行前在锁n上阻塞,执行完成后在锁n+1上唤醒;
- 以此类推 ..............
- 补充:
- 第1条任务执行前可以不阻塞,但执行完成后必须唤醒;(如果要阻塞,则可以让主线程来唤醒第1条任务);
- 补充: 最后一条任务执行后可以不唤醒,但执行前必须阻塞; (如果要唤醒,则最后一条任务执行完成后唤醒主线程);
- 与可重入锁类似,可以使用monitor监视器锁(多个);
- 与可重入锁类似,使用 Semaphore 信号量(多个);
- 与可重入锁类似,CountDownLatch : 倒计时锁存器(多个);
- 与可重入锁类似,CyclicBarrier 循环栅栏(多个) ;
【1】单个线程的线程池
参数设置:核心线程数=1, 最大线程数=1,就能保证线程池中只有1个线程在运行;
public class OrderlySingleThreadPoolTest {public static void main(String[] args) {ThreadPoolExecutor singleThreadPool =new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));singleThreadPool.execute(new Task(1));singleThreadPool.execute(new Task(2));singleThreadPool.execute(new Task(3));singleThreadPool.execute(new Task(4));singleThreadPool.execute(new Task(5));singleThreadPool.shutdown();}private static class Task implements Runnable {int order; // 执行序号Task(int order) {this.order = order;}@Overridepublic void run() {try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {throw new RuntimeException(e);}PrintUtils.print("序号=" + order + "执行完成");}}
}
【打印结果】
2024-06-09 16:07:59.398 序号=1执行完成
2024-06-09 16:08:02.404 序号=2执行完成
2024-06-09 16:08:05.411 序号=3执行完成
2024-06-09 16:08:08.425 序号=4执行完成
2024-06-09 16:08:11.439 序号=5执行完成
【2】thread.join()
main 调用 t1.join(),则main线程阻塞直到t1线程执行完成;如下。
public class ThreadJoinTest {public static void main(String[] args) {f1();PrintUtils.print("主线程结束");}public static void f1() {Thread t1 = new Thread(()->{try {TimeUnit.SECONDS.sleep(5);PrintUtils.print("t1线程结束");} catch (InterruptedException e) {throw new RuntimeException(e);}});t1.start();try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
2024-06-09 07:44:38.474 t1线程结束
2024-06-09 07:44:38.573 主线程结束
【3】可重入锁+条件变量实现多个线程顺序执行
1. 补充:
condition.await() 调用前需要获取锁;调用后释放锁(其他线程可以获取该锁,因此得名为可重入),但当前线程阻塞;
condition.signal() 调用前需要获取锁;调用后释放锁;
public class OrderlyReentrantLockTest {private static Condition[][] build(ReentrantLock reentrantLock, int num) {Condition[][] arr = new Condition[num][2];arr[0] = new Condition[]{null, reentrantLock.newCondition()};int i = 1;for (; i < num - 1; i++) {arr[i] = new Condition[]{arr[i - 1][1], reentrantLock.newCondition()};}arr[i] = new Condition[]{arr[i - 1][1], null};return arr;}public static void main(String[] args) {int threadNum = 5;ThreadPoolExecutor threadPool =new ThreadPoolExecutor(threadNum, threadNum, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));ReentrantLock reentrantLock = new ReentrantLock(true);AtomicInteger unWaitNum = new AtomicInteger(threadNum);// 构建条件变量数组Condition[][] conditionTwoArr = build(reentrantLock, threadNum);// 提交任务for (int order = threadNum; order >= 1; order--) {OrderlyTask orderlyTask = new OrderlyTask(reentrantLock, conditionTwoArr[order - 1], order, unWaitNum);threadPool.execute(orderlyTask);// 阻塞成功,才提交下一个任务while (unWaitNum.get() == threadNum) ; // 这里可能死循环,但可以新增超时重试机制来处理PrintUtils.print("阻塞成功,线程order=" + order);} threadPool.shutdown();}private static class OrderlyTask implements Runnable {private ReentrantLock lock;private Condition[] conditions;private int order; // 执行序号private AtomicInteger unWaitNum;OrderlyTask(ReentrantLock reentrantLock, Condition[] conditions, int order, AtomicInteger unWaitNum) {this.lock = reentrantLock;this.conditions = conditions;this.order = order;this.unWaitNum = unWaitNum;}@Overridepublic void run() {lock.lock();try {unWaitNum.decrementAndGet();try {if (conditions[0] != null) {conditions[0].await(); // 在第1个条件变量上阻塞}} catch (Exception e) {unWaitNum.incrementAndGet();throw e;}// 处理业务逻辑TimeUnit.SECONDS.sleep(3);// 唤醒在第2个条件变量上阻塞的线程if (conditions[1] != null) {conditions[1].signal();}} catch (Exception e) {System.err.println(e);} finally {lock.unlock();}PrintUtils.print("执行完成, 线程order=" + order + ", 线程id=" + Thread.currentThread().getName());}}
}
打印结果:
2024-06-09 22:16:00.696 阻塞成功,线程order=5
2024-06-09 22:16:00.698 阻塞成功,线程order=4
2024-06-09 22:16:00.698 阻塞成功,线程order=3
2024-06-09 22:16:00.698 阻塞成功,线程order=2
2024-06-09 22:16:00.698 阻塞成功,线程order=1
2024-06-09 22:16:03.707 执行完成, 线程order=1, 线程id=pool-1-thread-5
2024-06-09 22:16:06.719 执行完成, 线程order=2, 线程id=pool-1-thread-4
2024-06-09 22:16:09.719 执行完成, 线程order=3, 线程id=pool-1-thread-3
2024-06-09 22:16:12.727 执行完成, 线程order=4, 线程id=pool-1-thread-2
2024-06-09 22:16:15.729 执行完成, 线程order=5, 线程id=pool-1-thread-1
【4】使用CountDownLatch倒计时锁存器
public class OrderlyCountDownLatchTest {private static CountDownLatch[][] build(int num) {CountDownLatch[][] arr = new CountDownLatch[num][2];arr[0] = new CountDownLatch[]{null, new CountDownLatch(1)};int i = 1;for (; i < num - 1; i++) {arr[i] = new CountDownLatch[]{arr[i - 1][1], new CountDownLatch(1)};}arr[i] = new CountDownLatch[]{arr[i - 1][1], null};return arr;}public static void main(String[] args) {int threadNum = 5;ThreadPoolExecutor threadPool =new ThreadPoolExecutor(threadNum, threadNum, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));AtomicInteger unWaitNum = new AtomicInteger(threadNum);// 构建倒计时锁存器数组CountDownLatch[][] latchArr = build(threadNum);// 提交任务for (int order = threadNum; order >= 1; order--) {OrderlyTask orderlyTask = new OrderlyTask(latchArr[order - 1], order, unWaitNum);threadPool.execute(orderlyTask);// 阻塞成功,才提交下一个任务while (unWaitNum.get() == threadNum) ; // 这里可能死循环,但可以新增超时重试机制来处理PrintUtils.print("阻塞成功,线程order=" + order);}threadPool.shutdown();}private static class OrderlyTask implements Runnable {private CountDownLatch[] latchArr;private int order; // 执行序号private AtomicInteger unWaitNum;OrderlyTask(CountDownLatch[] latchArr, int order, AtomicInteger unWaitNum) {this.latchArr = latchArr;this.order = order;this.unWaitNum = unWaitNum;}@Overridepublic void run() {try {unWaitNum.decrementAndGet();try {if (latchArr[0] != null) {latchArr[0].await(); // 在第1个锁存器上阻塞}} catch (Exception e) {unWaitNum.incrementAndGet();throw e;}// 处理业务逻辑TimeUnit.SECONDS.sleep(3);// 唤醒在第2个条件变量上阻塞的线程if (latchArr[1] != null) {latchArr[1].countDown();}} catch (Exception e) {System.err.println(e);}PrintUtils.print("执行完成, 线程order=" + order + ", 线程id=" + Thread.currentThread().getName());}}
}
打印结果:
2024-06-09 22:35:13.648 阻塞成功,线程order=5
2024-06-09 22:35:13.651 阻塞成功,线程order=4
2024-06-09 22:35:13.651 阻塞成功,线程order=3
2024-06-09 22:35:13.651 阻塞成功,线程order=2
2024-06-09 22:35:13.651 阻塞成功,线程order=1
2024-06-09 22:35:16.664 执行完成, 线程order=1, 线程id=pool-1-thread-5
2024-06-09 22:35:19.676 执行完成, 线程order=2, 线程id=pool-1-thread-4
2024-06-09 22:35:22.682 执行完成, 线程order=3, 线程id=pool-1-thread-3
2024-06-09 22:35:25.684 执行完成, 线程order=4, 线程id=pool-1-thread-2
2024-06-09 22:35:28.688 执行完成, 线程order=5, 线程id=pool-1-thread-1
相关文章:
java并发-如何保证线程按照顺序执行?
【readme】 使用只有单个线程的线程池(最简单)Thread.join() 可重入锁 ReentrantLock Condition 条件变量(多个) ; 原理如下: 任务1执行前在锁1上阻塞;执行完成后在锁2上唤醒;任务…...
PyCharm中 Fitten Code插件的使用说明一
一. 简介 Fitten Code插件是是一款由非十大模型驱动的 AI 编程助手,它可以自动生成代码,提升开发效率,帮您调试 Bug,节省您的时间,另外还可以对话聊天,解决您编程碰到的问题。 前一篇文章学习了 PyCharm…...
Polar Web【简单】PHP反序列化初试
Polar Web【简单】PHP反序列化初试 Contents Polar Web【简单】PHP反序列化初试思路EXP手动脚本PythonGo 运行&总结 思路 启动环境,显示下图中的PHP代码,于是展开分析: 首先发现Easy类中有魔术函数 __wakeup() ,实现的是对成员…...
树莓派4B 零起点(二) 树莓派 更换软件源和软件仓库
目录 一、准备工作,查看自己的树莓派版本 二、安装HTTPS支持 三、更换为清华源 1、更换Debian软件源 2,更换Raspberrypi软件仓库 四、进行软件更新 接前章,我们的树莓派已经启动起来了,接下来要干的事那就是更换软件源和软件…...
Pytorch 实现目标检测二(Pytorch 24)
一 实例操作目标检测 下面通过一个具体的例子来说明锚框标签。我们已经为加载图像中的狗和猫定义了真实边界框,其中第一个 元素是类别(0代表狗,1代表猫),其余四个元素是左上角和右下角的(x, y)轴坐标(范围…...
如何使用Python中的列表解析(list comprehension)进行高效列表操作
Python中的列表解析(list comprehension)是一种创建列表的简洁方法,它可以在单行代码中执行复杂的循环和条件逻辑。列表解析提供了一种快速且易于阅读的方式来生成新的列表。 以下是一些使用列表解析进行高效列表操作的示例: 1.…...
java使用websocket遇到的问题
java使用websocket的bug 1 websocket连接正常但是收不到服务端发出的消息java的websocket并发的时候导致连接断开(看着连接是正常的,但是实际上已经断开) 1 websocket连接正常但是收不到服务端发出的消息 java的websocket并发的时候导致连接断…...
[Cloud Networking] Layer 2
文章目录 1. 什么是Mac Address?2. 如何查找MAC地址?3. 二层数据交换4. [Layer 2 Protocol](https://blog.csdn.net/settingsun1225/article/details/139552315) 1. 什么是Mac Address? MAC 地址是计算机的唯一48位硬件编码,嵌入到网卡中。 MAC地址也…...
[240609] qwen2 发布,在 Ollama 已可用 | 采用语言模型构建通用 AGI(2020年8月)
目录 qwen2 发布,在 Ollama 已可用Qwen2 模型概览 (基于 Ollama 网站信息)一、模型介绍二、模型参数三、支持语言 (除英语和中文外)四、模型性能五、许可证六、数据支撑: 采用语言模型构建通用 AGI qwen2 发布,在 Ollama 已可用 Qwen2 模型概览 (基于 O…...
赶紧收藏!2024 年最常见 20道分布式、微服务面试题(五)
上一篇地址:赶紧收藏!2024 年最常见 20道分布式、微服务面试题(四)-CSDN博客 九、在分布式系统中,如何保证数据一致性? 在分布式系统中保证数据一致性是一个复杂的问题,因为分布式系统由多个独…...
为什么Kubernetes(K8S)弃用Docker:深度解析与未来展望
为什么Kubernetes弃用Docker:深度解析与未来展望 🚀 为什么Kubernetes弃用Docker:深度解析与未来展望摘要引言正文内容(详细介绍)什么是 Kubernetes?什么是 Docker?Kubernetes 和 Docker 的关系…...
软件游戏提示msvcp120.dll丢失的解决方法,总结多种靠谱的解决方法
在电脑使用过程中,我们可能会遇到一些错误提示,其中之一就是“找不到msvcp120.dll”。那么,msvcp120.dll是什么?它对电脑有什么影响?有哪些解决方法?本文将从以下几个方面进行探讨。 一,了解msv…...
使用kafka tools工具连接带有用户名密码的kafka
使用kafka tools工具连接带有用户名密码的kafka 创建kafka连接,配置zookeeper 在Security选择Type类型为SASL Plaintext 在Advanced页面添加如下图红框框住的内容 在JAAS_Config加上如下配置 需要加的配置: org.apache.kafka.common.security.plain.Pla…...
[个人感悟] Java基础问题应该考察哪些问题?
前言 “一切代码无非是数据结构和算法流程的结合体.” 忘了最初是在何处看见这句话了, 这句话, 对于Java基础的考察也是一样. 正如这句话所说, 我们对于基础的考察主要考察, 数据结构, 集合类型结构, 异常类型, 已经代码的调用和语法关键字. 其中数据结构和集合类型结构是重点…...
MySQL-主从复制
1、主从复制的理解 在工作用常见Redis作为缓存与MySQL一起使用。当有请求时,首先会从缓存中进行查找,如果存在就直接取出,否则访问数据库,这样 提升了读取的效率,也减少了对后台数据库的访问压力。Redis的缓存架构时高…...
开发没有尽头,尽力既是完美
最近遇到了一些难题,开发系统总有一些地方没有考虑周全,偏偏用户使用的时候“完美复现”了这个隐藏的Bug...... 讲道理创业一年之久为了生存,我一直都有在做复盘,复盘的核心就是:如何提升营收、把控开发质量࿰…...
【手推公式】如何求SDE的解(附录B)
【手推公式】如何求SDE的解(附录B) 核心思路:不直接求VE和VP的SDE的解xt,而是求xt的期望和方差,从而写出x0到xt的条件分布形式(附录B) 论文:Score-Based Generative Modeling throug…...
STM32F103单片机工程移植到航顺单片机HK32F103注意事项
一、简介 作为国内MCU厂商中前三阵营之一的航顺芯片,建立了世界首创超低功耗7nA物联网、万物互联核心处理器浩瀚天际10X系列平台,接受代理商/设计企业/方案商定制低于自主研发十倍以上成本,接近零风险自主品牌产品,芯片设计完成只…...
Llama模型家族之Stanford NLP ReFT源代码探索 (四)Pyvene论文学习
LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 (一) 基于 LlaMA 3 LangGraph 在windows本地部署大模型 (二) 基于 LlaMA 3 LangGraph 在windows本地部署大模型 (三) 基于 LlaMA…...
rapidjson 打包过程插入对象
开发过程中遇到一种情况,在打包过程中插入一个字符串(里面是json对象), 官方文档 没看到相关例子,不知道是不是自己粗心没找到。方法RawValue其实是一个通用打包方法,一般情况我们都调用的是String()、Int(…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving
地址:LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂,正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...
文件上传漏洞防御全攻略
要全面防范文件上传漏洞,需构建多层防御体系,结合技术验证、存储隔离与权限控制: 🔒 一、基础防护层 前端校验(仅辅助) 通过JavaScript限制文件后缀名(白名单)和大小,提…...
SQL进阶之旅 Day 22:批处理与游标优化
【SQL进阶之旅 Day 22】批处理与游标优化 文章简述(300字左右) 在数据库开发中,面对大量数据的处理任务时,单条SQL语句往往无法满足性能需求。本篇文章聚焦“批处理与游标优化”,深入探讨如何通过批量操作和游标技术提…...
CppCon 2015 学习:Simple, Extensible Pattern Matching in C++14
什么是 Pattern Matching(模式匹配) ❝ 模式匹配就是一种“描述式”的写法,不需要你手动判断、提取数据,而是直接描述你希望的数据结构是什么样子,系统自动判断并提取。❞ 你给的定义拆解: ✴ Instead of …...
