【多线程】如何实现三个线程循环打印ABC

🥰🥰🥰来都来了,不妨点个关注叭!
👉博客主页:欢迎各位大佬!👈

在这里插入图片描述

文章目录

  • 1. 如何实现三个线程顺序打印ABC
    • 1.1 详细要求描述
    • 1.2 使用join()
    • 1.3 实现代码
    • 1.4 运行结果
  • 2. 如何实现三个线程循环打印ABC
    • 2.1 详细要求描述
    • 2.2 使用wait()和notifyAll()方法
    • 2.3 补充:竞态条件
    • 2.4 实现代码
    • 2.5 运行结果
    • 2.6 为什么使用这些 —— 细节探讨
      • 2.6.1 为什么用 notifyAll,而不是 notify
      • 2.6.2 为什么用 while(count % 3 != ?) 判断,而不是 if

1. 如何实现三个线程顺序打印ABC

1.1 详细要求描述

描述:实现三个线程按顺序打印 ABC,并且只打印一次!
输出:ABC

1.2 使用join()

这里很容易想到使用 join() 方法

在 Thread类及其基本用法 这期内容中详细介绍 join()方法

join()方法的作用

线程的调度是无序的,哪个线程先执行,我们无从知晓,而使用 join() 方法,可以明确控制线程结束的执行顺序!

需要理解其用法:在哪个线程调用该线程的join()方法,就是让哪个线程等待该线程结束

通过 join() 方法,可以实现线程之间的同步,如在 t1 线程中,调用 t2.join() 即让 t1 线程 等待 t2 线程结束,t1 线程阻塞等待,而其它线程正常调度,这样 t2 线程先执行,t1 线程会等待 t2 线程执行完毕后再继续执行,明确控制了线程结束执行顺序

1.3 实现代码

public class PrintABC1 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{System.out.println("A");});Thread t2 = new Thread(()->{System.out.println("B");});Thread t3 = new Thread(()->{System.out.println("C");});t1.start();t1.join();t2.start();t2.join();t3.start();t3.join();}
}

1.4 运行结果

符合预期:

在这里插入图片描述

2. 如何实现三个线程循环打印ABC

2.1 详细要求描述

描述:有三个线程,每个线程只能打印一个字母,分别为 A,B,C,要求按照这个 ABC 的顺序,打印 10 次!
输出:A B C A B C A B C A B C A B C A B C A B C A B C A B C A B C

2.2 使用wait()和notifyAll()方法

我们可能会想:

  • 使用 join() ? (×)
    在这个场景中,特点是循环打印,而不是打印一次,在循环打印的条件下,其实join()方法就不太适用了,因为线程 t1、t2、t3 里的代码需要循环执行多次,join()方法会阻塞当前线程直到另一个线程完成,如果按顺序在每个线程打印后调用 join(),虽然可以确保执行的顺序,但是不灵活且效率低下,尤其是需要多次重复执行的时候,且不好实现
  • 使用 sleep() ?(×)
    仍然无法保证执行顺序,具有不确定性,sleep()方法只是让当前线程暂停执行指定的时间,不能确保其它线程在这段时间内完成了工作!!!,这可能导致 “竞态条件” ,同样,效率比较低下,因为它需要猜测其他线程完成所需的时间,通常是不准确(这怎么好猜!)

相比之下,在上述场景中,使用 wait()方法和notifyAll()方法可以更好实现线程之间的同步!可以让线程在需要等待的时候进入等待状态,当满足某个条件的时候,再唤醒该线程,执行其内容

在 wait()和notify() 这期内容中,具体介绍了 wait()和notifyAll()方法

循环打印ABC,具体思路如下:

1) 定义共享变量

  • count:用于控制打印的次数,初始化为1
  • MAX_COUNT:定义打印的总次数,为10

2)创建并启动线程:

  • 创建三个线程t1,t2,t3,分别代表打印A,B, C的线程
  • 每个线程内部都通过一个for循环来控制打印的次数,循环条件是 i < abc.MAX_COUNT,实际的打印次数控制是通过 count 变量和wait()、notifyAll()来实现的

3)线程同步和通信以及打印操作

  • 创建一个锁对象,abc,其中 abc 是PrintABC类的一个实例,使用synchronized (abc)来确保在同一时刻只有一个线程可以执行打印操作
  • 在每个线程中,通过 while 循环检查count % 3 的值来确定当前线程是否应该执行打印操作
  • 如果当前线程不应该执行打印,则调用abc.wait()进入等待状态,直到其他线程调用abc.notifyAll()唤醒它
  • 当线程执行完打印操作后,将count+1,并调用abc.notifyAll()唤醒所有可能因条件不满足而等待的线程

4)输出结果

最终,程序将输出10次A B C的序列,每次中A,B,C 按顺序被打印出来

图解说明

在这里插入图片描述

2.3 补充:竞态条件

含义】指在多线程环境中,两个或多个线程在访问共享资源时,由于执行顺序的不确定性而导致的程序输出结果与预期不符或程序状态错误的现象,即由于线程执行的时间顺序不一致,导致程序的行为变得不可预测

可能发生的情况

  • 多个线程同时读写共享资源:当多个线程对同一个资源进行读和写操作时,如果没有适当的同步机制来控制访问顺序,就可能导致数据不一致
  • 条件竞争:线程之间在执行顺序上存在竞争关系,某个线程的执行结果依赖于其他线程的执行进度,如果这种依赖关系没有得到妥善处理,就很容易导致竞态条件

竞态条件可能产生的影响

  • 数据不一致:共享资源中的数据被错误地修改,导致数据与预期的不符合
  • 程序崩溃:在某些情况下,竞态条件可能导致程序崩溃或异常终止
  • 死锁:在复杂的同步机制中,竞态条件可能引发死锁

如何避免竞态条件

通常需要使用同步机制来控制线程对共享资源的访问,这些同步机制包括有:

  • 互斥锁(Mutexes)
  • 信号量(Semaphores)
  • 读写锁(Read-Write Locks)
  • 条件变量(Condition Variables)
  • Java 中的 synchronized关键字、ReentrantLock类

2.4 实现代码

public class PrintABC {private int count = 1; // 控制打印的轮次private final int MAX_COUNT = 10; // 打印的总轮次public static void main(String[] args) {PrintABC abc = new PrintABC();Thread t1 = new Thread(() -> {for (int i = 0; i < abc.MAX_COUNT; ) {synchronized (abc) {while (abc.count % 3 != 1) {try {abc.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("A ");abc.count++;abc.notifyAll();i++;}}});Thread t2 = new Thread(() -> {for (int i = 0; i < abc.MAX_COUNT; ) {synchronized (abc) {while (abc.count % 3 != 2) {try {abc.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("B ");abc.count++;abc.notifyAll();i++;}}});Thread t3 = new Thread(() -> {for (int i = 0; i < abc.MAX_COUNT; ) {synchronized (abc) {while (abc.count % 3 != 0) {try {abc.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("C ");abc.count++;abc.notifyAll();i++;}}});t1.start();t2.start();t3.start();}
}

2.5 运行结果

符合预期:

在这里插入图片描述

2.6 为什么使用这些 —— 细节探讨

2.6.1 为什么用 notifyAll,而不是 notify

在上述代码中,使用的是notifyAll()方法来通知其它所有线程,而并不是使用notify()方法,因为在上述代码中,并不是只是涉及到两个线程,涉及到三个线程,当 t1 线程执行时候,t2、t3 线程条件不满足,都处于 wait 状态,notify()方法只会随机唤醒一个等待该锁对象的线程notifAll()方法则是唤醒所有等待该对象锁的线程,因为我们期望通知到所有的等待线程,再根据条件判断,是否继续等待,还是可以被唤醒执行,因此,在这里使用 notifyAll()方法!

2.6.2 为什么用 while(count % 3 != ?) 判断,而不是 if

其实在之前的内容中,提到过~ 不知道各位小伙伴还有木有印象,可以回顾这期内容:阻塞队列,改进优化,将 if 改为 while!

为什么要这样做呢?

因为 Java官方并不建议这么使用 wait()方法,即并不建议使用 if 判断,而是使用 while 判断,尽管这里的三个线程并没有暗含着interrupt() 方法,但使用while可以避免虚假唤醒!

可以看到如果把 while 改成 if,顺序是有问题的:

在这里插入图片描述
我们可以进行仔细分析,如果此时 count 为 1,则只有 A 可以打印,B 和 C 因为唤醒条件不满足,进入 wait 等待状态,如果 B 和 C 可以打印,则是虚假唤醒,试想一下,如果是 if 判断,进行了一次 if 判断之后,B 和 C 进入 wait 等待状态,等到 A 执行完毕后,此时 count+1, count 为 2,唤醒所有线程,此时打印 B,C 应该进入等待状态,但是之前已经判断过一次,所以A 和 C 也被唤醒了,此时就是虚假唤醒,和 B 所在的线程抢占式执行,此后的 t1、t2、t3 线程执行顺序也就不可控了

因此,这里需要,在 wait()方法唤醒后,再判定一次条件,wait() 之前,发现条件不满足,开始 wait(),等到 wait() 唤醒后再确认一下条件是否满足,如果不满足,还可以继续 wait()!故用 while 循环判断~

✨✨✨本期内容到此结束啦~ 下期再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/397302.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小白零基础学数学建模系列-Day6-微分方程模型基础与案例

文章目录 1 微分方程模型概述1.1 微分方程的基本概念和分类1.2 微分方程在科学计算中的重要性1.3 使用Python进行微分方程建模的优势 2 Python在微分方程建模中的应用2.1 常用Python库介绍2.2 Python的符号计算与解析解2.3 Python的数值计算与数值解 3 解微分方程的基本方法与P…

【大学物理】第7章 机械波,平面简谐波的方程,能流密度,惠更斯原理,波的叠加原理,波的干涉,驻波,多普勒原理(清华大学)

目录 7-1 波的基本概念 一、机械波产生的条件 二、横波和纵波 三、波线和波面 四、简谐波 五、物体的弹性形变* 1. 长变 2. 切变 3.容变 六、描述波动的几个物理量 1.波速 u 2.波动周期和频率 3.波长入 7.2 平面简谐波的方程 一、平面简谐波的波动方程 1.一…

即时通讯系统选型:如何为企业选择最佳的私有化即时通讯工具

在企业内部沟通和协作中&#xff0c;选择适合的即时通讯工具对于保障数据安全和提高工作效率至关重要。随着私有化即时通讯工具的出现&#xff0c;企业可以更好地掌握自身数据和系统&#xff0c;并提供更高的安全性和定制化能力。本文将为您提供一些指导&#xff0c;帮助企业选…

数学建模学习笔记

数学建模学习笔记 现学现卖&#xff0c;随缘更新QwQ 主要根据b站大师兄的视频整理而成&#xff0c;有不懂的可以去看原视频 List 数学建模学习笔记一、 层次分析法1.1 矩阵的一致性及其检验1.2 权重计算1.3 具体流程 二、模糊综合评测2.1 隶属函数2.2 隶属函数的确定方法2.3 模…

LeetCode 热题 HOT 100 (031/100)【宇宙最简单版】

【二叉树】No. 0437 路径总和 III【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&am…

开发者账号验证难题,身份证居然不管用了!聊聊怎么搞定地址证明

最近不少开发者遇到了一个棘手的问题&#xff1a;Google Play的开发者账号验证突然变得更难了&#xff0c;尤其是身份证竟然不能再作为地址证明用了&#xff01; 今天分享一下开发者朋友们成功的经历和一些实用的小技巧&#xff0c;希望能帮大家顺利通过Google Play的身份验证。…

Redis的持久化的策略

Redis的持久化的策略 官方文档说明 AOF持久化策略RDB持久化的策略 AOF持久化策略 AOF持久性记录服务器接收到的每个写操作&#xff0c;然后&#xff0c;可以在服务器启动时再次重播这些操作&#xff0c;重建原始数据集&#xff0c;使用与Redis协议本身相同的格式记录命令。…

英语词汇量测试系统、英语学习系统

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&…

Wireshark_DNS_v7.0

Wireshark_DNS_v7.0 一、 nslookup 前置 nslookup 是一个网络命令行工具&#xff0c;用于查询域名系统&#xff08;DNS&#xff09;中的域名解析记录。通过使用 nslookup&#xff0c;你可以获取某个域名的IP地址&#xff0c;或者获取与某个IP地址关联的域名信息。 查看域名…

Kafka + Kraft 集群搭建教程,附详细配置及自动化安装脚本

本文主要介绍 kafka kraft 搭建过程&#xff0c;主要用途是为了日志采集&#xff0c;所以搭建相对比较简单暴力&#xff0c;不过也可以作为一个参考供大家学习&#xff0c;主打一个能用管跑&#xff08;调优啊&#xff0c;参数解释啊&#xff0c;原理啊&#xff0c;太枯燥了&a…

Java高级面试题(二)-- JVM

Jvm虚拟机&#xff0c;运行在操作系统之上&#xff0c;编译执行java代码 1, 面试官&#xff1a;手绘一个类加载过程 补充&#xff1a; 这里的执行硬件 java 调用 c 指令 创建线程 &#xff0c;new thread()->start() 底层代码就是 native start0&#xff08;&#xff09;&…

SuccBI+低代码文档中心 — 分析报表

报表设计器界面介绍 报表设计器是用来设计报表的可视化工具&#xff0c;使用类Excel的、基于单元格的、所见即所得的方式设计报表&#xff0c;报表设计器界面如下图&#xff1a; 报表设计器主要包括如下功能板块&#xff1a; 数据源&#xff1a;数据源区域列出了可以用于报表…

haproxy高级功能配置

介绍HAProxy高级配置及实用案例 一.基于cookie会话保持 cookie value:为当前server指定cookie值&#xff0c;实现基于cookie的会话黏性&#xff0c;相对于基于 source 地址hash 调度算法对客户端的粒度更精准&#xff0c;但同时也加大了haproxy负载&#xff0c;目前此模式使用…

OpenGL ES->工作机制

渲染流程 渲染目的&#xff1a;输入3D立体坐标&#xff0c;输出绘制后的2D平面像素工作流程&#xff1a;顶点着色器->图元装配->几何着色器->光栅化->片段着色器->测试与混合&#xff0c;整个工作流程被封装在GPU内部&#xff0c;无法改变。运行在CPU的代码调用…

acpi 主板布局需要 efi

今天在折腾 ESXI 的时候&#xff0c;启动虚拟机跳出了 acpi 主板布局需要 efi 然后我就将 ESXI 的启动方式改为了 EFI 但是虚拟机有莫名的启动不了&#xff0c;网上也没有找到办法&#xff0c;最后&#xff0c;我将虚拟机类型有原本的 ubuntu 换成了 debian 最后启动成功&…

【弱监督时间动作定位】ACGNet: Action Complement Graph Network for WSTAL 论文阅读

ACGNet: Action Complement Graph Network for Weakly-supervised Temporal Action Localization 论文阅读 AbstractIntroductionRelated WorkAction Complement Graph NetworkMethod OverviewAction Complement GraphGraph InferenceTraining Objective ExperimentsConclusion…

nvm node yarn 的安装教程

一、完全卸载旧的nodejs 1、控制面板卸载 nodejs 2、删除node的安装目录 3、删除C盘中遗留的文件 4、将有关node的环境变量删除 5、查看卸载是否成功 npm -v node -v二、安装并配置NVM 1、下载NVM 地址&#xff1a;https://github.com/coreybutler/nvm-windows/release…

移除元素OJ详解

一、题目介绍 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xff0c;要通过此题&#xff0c;您需要执行以下操作&am…

资料分析公式

年均增长量 年均增长率

SpringIOC和SpringAOC

lombok插件 XML<!-- 加载资源文件 --><context:property-placeholder location"classpath:jdbc.properties"></context:property-placeholder><!-- 注入数据源 --><bean id"dataSource" class"com.mchange.v2.c3p0.ComboP…