当前位置: 首页 > news >正文

java并发-如何保证线程按照顺序执行?

【readme】

  1. 使用只有单个线程的线程池(最简单
  2. Thread.join() 
  3. 可重入锁 ReentrantLock + Condition 条件变量(多个) ; 原理如下:
    1. 任务1执行前在锁1上阻塞;执行完成后在锁2上唤醒;
    2. 任务2执行前在锁2上阻塞,执行完成后在锁3上唤醒;
    3. 任务n执行前在锁n上阻塞,执行完成后在锁n+1上唤醒;
    4. 以此类推 ..............
    5. 补充:
      1. 第1条任务执行前可以不阻塞,但执行完成后必须唤醒;(如果要阻塞,则可以让主线程来唤醒第1条任务);
      2. 补充: 最后一条任务执行后可以不唤醒,但执行前必须阻塞; (如果要唤醒,则最后一条任务执行完成后唤醒主线程)
  4. 与可重入锁类似,可以使用monitor监视器锁(多个);
  5. 与可重入锁类似,使用 Semaphore 信号量(多个);
  6. 与可重入锁类似,CountDownLatch : 倒计时锁存器(多个); 
  7. 与可重入锁类似,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】 使用只有单个线程的线程池&#xff08;最简单&#xff09;Thread.join() 可重入锁 ReentrantLock Condition 条件变量&#xff08;多个&#xff09; &#xff1b; 原理如下&#xff1a; 任务1执行前在锁1上阻塞&#xff1b;执行完成后在锁2上唤醒&#xff1b;任务…...

PyCharm中 Fitten Code插件的使用说明一

一. 简介 Fitten Code插件是是一款由非十大模型驱动的 AI 编程助手&#xff0c;它可以自动生成代码&#xff0c;提升开发效率&#xff0c;帮您调试 Bug&#xff0c;节省您的时间&#xff0c;另外还可以对话聊天&#xff0c;解决您编程碰到的问题。 前一篇文章学习了 PyCharm…...

Polar Web【简单】PHP反序列化初试

Polar Web【简单】PHP反序列化初试 Contents Polar Web【简单】PHP反序列化初试思路EXP手动脚本PythonGo 运行&总结 思路 启动环境&#xff0c;显示下图中的PHP代码&#xff0c;于是展开分析&#xff1a; 首先发现Easy类中有魔术函数 __wakeup() &#xff0c;实现的是对成员…...

树莓派4B 零起点(二) 树莓派 更换软件源和软件仓库

目录 一、准备工作&#xff0c;查看自己的树莓派版本 二、安装HTTPS支持 三、更换为清华源 1、更换Debian软件源 2&#xff0c;更换Raspberrypi软件仓库 四、进行软件更新 接前章&#xff0c;我们的树莓派已经启动起来了&#xff0c;接下来要干的事那就是更换软件源和软件…...

Pytorch 实现目标检测二(Pytorch 24)

一 实例操作目标检测 下面通过一个具体的例子来说明锚框标签。我们已经为加载图像中的狗和猫定义了真实边界框&#xff0c;其中第一个 元素是类别&#xff08;0代表狗&#xff0c;1代表猫&#xff09;&#xff0c;其余四个元素是左上角和右下角的(x, y)轴坐标&#xff08;范围…...

如何使用Python中的列表解析(list comprehension)进行高效列表操作

Python中的列表解析&#xff08;list comprehension&#xff09;是一种创建列表的简洁方法&#xff0c;它可以在单行代码中执行复杂的循环和条件逻辑。列表解析提供了一种快速且易于阅读的方式来生成新的列表。 以下是一些使用列表解析进行高效列表操作的示例&#xff1a; 1.…...

java使用websocket遇到的问题

java使用websocket的bug 1 websocket连接正常但是收不到服务端发出的消息java的websocket并发的时候导致连接断开&#xff08;看着连接是正常的&#xff0c;但是实际上已经断开&#xff09; 1 websocket连接正常但是收不到服务端发出的消息 java的websocket并发的时候导致连接断…...

[Cloud Networking] Layer 2

文章目录 1. 什么是Mac Address?2. 如何查找MAC地址&#xff1f;3. 二层数据交换4. [Layer 2 Protocol](https://blog.csdn.net/settingsun1225/article/details/139552315) 1. 什么是Mac Address? MAC 地址是计算机的唯一48位硬件编码&#xff0c;嵌入到网卡中。 MAC地址也…...

[240609] qwen2 发布,在 Ollama 已可用 | 采用语言模型构建通用 AGI(2020年8月)

目录 qwen2 发布&#xff0c;在 Ollama 已可用Qwen2 模型概览 (基于 Ollama 网站信息)一、模型介绍二、模型参数三、支持语言 (除英语和中文外)四、模型性能五、许可证六、数据支撑: 采用语言模型构建通用 AGI qwen2 发布&#xff0c;在 Ollama 已可用 Qwen2 模型概览 (基于 O…...

赶紧收藏!2024 年最常见 20道分布式、微服务面试题(五)

上一篇地址&#xff1a;赶紧收藏&#xff01;2024 年最常见 20道分布式、微服务面试题&#xff08;四&#xff09;-CSDN博客 九、在分布式系统中&#xff0c;如何保证数据一致性&#xff1f; 在分布式系统中保证数据一致性是一个复杂的问题&#xff0c;因为分布式系统由多个独…...

为什么Kubernetes(K8S)弃用Docker:深度解析与未来展望

为什么Kubernetes弃用Docker&#xff1a;深度解析与未来展望 &#x1f680; 为什么Kubernetes弃用Docker&#xff1a;深度解析与未来展望摘要引言正文内容&#xff08;详细介绍&#xff09;什么是 Kubernetes&#xff1f;什么是 Docker&#xff1f;Kubernetes 和 Docker 的关系…...

软件游戏提示msvcp120.dll丢失的解决方法,总结多种靠谱的解决方法

在电脑使用过程中&#xff0c;我们可能会遇到一些错误提示&#xff0c;其中之一就是“找不到msvcp120.dll”。那么&#xff0c;msvcp120.dll是什么&#xff1f;它对电脑有什么影响&#xff1f;有哪些解决方法&#xff1f;本文将从以下几个方面进行探讨。 一&#xff0c;了解msv…...

使用kafka tools工具连接带有用户名密码的kafka

使用kafka tools工具连接带有用户名密码的kafka 创建kafka连接&#xff0c;配置zookeeper 在Security选择Type类型为SASL Plaintext 在Advanced页面添加如下图红框框住的内容 在JAAS_Config加上如下配置 需要加的配置&#xff1a; org.apache.kafka.common.security.plain.Pla…...

[个人感悟] Java基础问题应该考察哪些问题?

前言 “一切代码无非是数据结构和算法流程的结合体.” 忘了最初是在何处看见这句话了, 这句话, 对于Java基础的考察也是一样. 正如这句话所说, 我们对于基础的考察主要考察, 数据结构, 集合类型结构, 异常类型, 已经代码的调用和语法关键字. 其中数据结构和集合类型结构是重点…...

MySQL-主从复制

1、主从复制的理解 在工作用常见Redis作为缓存与MySQL一起使用。当有请求时&#xff0c;首先会从缓存中进行查找&#xff0c;如果存在就直接取出&#xff0c;否则访问数据库&#xff0c;这样 提升了读取的效率&#xff0c;也减少了对后台数据库的访问压力。Redis的缓存架构时高…...

开发没有尽头,尽力既是完美

最近遇到了一些难题&#xff0c;开发系统总有一些地方没有考虑周全&#xff0c;偏偏用户使用的时候“完美复现”了这个隐藏的Bug...... 讲道理创业一年之久为了生存&#xff0c;我一直都有在做复盘&#xff0c;复盘的核心就是&#xff1a;如何提升营收、把控开发质量&#xff0…...

【手推公式】如何求SDE的解(附录B)

【手推公式】如何求SDE的解&#xff08;附录B&#xff09; 核心思路&#xff1a;不直接求VE和VP的SDE的解xt&#xff0c;而是求xt的期望和方差&#xff0c;从而写出x0到xt的条件分布形式&#xff08;附录B&#xff09; 论文&#xff1a;Score-Based Generative Modeling throug…...

STM32F103单片机工程移植到航顺单片机HK32F103注意事项

一、简介 作为国内MCU厂商中前三阵营之一的航顺芯片&#xff0c;建立了世界首创超低功耗7nA物联网、万物互联核心处理器浩瀚天际10X系列平台&#xff0c;接受代理商/设计企业/方案商定制低于自主研发十倍以上成本&#xff0c;接近零风险自主品牌产品&#xff0c;芯片设计完成只…...

Llama模型家族之Stanford NLP ReFT源代码探索 (四)Pyvene论文学习

LlaMA 3 系列博客 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;一&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;二&#xff09; 基于 LlaMA 3 LangGraph 在windows本地部署大模型 &#xff08;三&#xff09; 基于 LlaMA…...

rapidjson 打包过程插入对象

开发过程中遇到一种情况&#xff0c;在打包过程中插入一个字符串&#xff08;里面是json对象&#xff09;&#xff0c; 官方文档 没看到相关例子&#xff0c;不知道是不是自己粗心没找到。方法RawValue其实是一个通用打包方法&#xff0c;一般情况我们都调用的是String()、Int(…...

Oracle查询表空间大小

1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

【python异步多线程】异步多线程爬虫代码示例

claude生成的python多线程、异步代码示例&#xff0c;模拟20个网页的爬取&#xff0c;每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程&#xff1a;允许程序同时执行多个任务&#xff0c;提高IO密集型任务&#xff08;如网络请求&#xff09;的效率…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...