JavaWeb12-线程通讯(线程等待和唤醒)
目录
1.方法介绍
1.1.wait()/wait(long timeout):让当前线程进入等待状态。
1.1.1.wait执行流程
1.1.2.wait结束等待的条件
1.1.3.wait() VS wait(long timeout)
1.1.4.为什么wait要放在Object中?
--->PS:wait(0) 和 sleep(0) 的区别
--->PS:wait 和 sleep 释放锁行为的区别
--->PS:(常见面试题)wait 和 sleep 的区别(小结)
--->PS:3种方法让线程进入休眠/等待状态
1.2.notify():唤醒当前对象上一个休眠的线程(随机)。
1.3.notifyAll():唤醒当前对象上的所有线程。
2.注意事项:
由于线程之间是抢占式执⾏的,因此线程之间执⾏的先后顺序难以预知。但是实际开发中有时候希望合理地协调多个线程之间的执⾏先后顺序。
球场上的每个运动员都是独⽴的 "执⾏流",可以认为是⼀个 "线程"。⽽完成⼀个具体的进攻得分动作,则需要多个运动员相互配合, 按照⼀定的顺序执⾏⼀定的动作,线程1 先 "传球",线程2 才能 "扣篮"。
1.方法介绍
完成这个协调⼯作(线程通讯),主要涉及到以下三个⽅法。注意这三个方法都是对象级别的(需要通过"对象."来调用),【不是锁级别,如Thread.sleep()】都是Object类的内置方法。
1.1.wait()/wait(long timeout):让当前线程进入等待状态。
1.1.1.wait执行流程
- 使当前执⾏代码的线程进⾏等待。(把线程放到等待队列中)。
- 释放当前的锁。
- 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁。
1.1.2.wait结束等待的条件
- 其他线程调⽤该对象的 notify ⽅法。wait/notify 唤醒顺序是无序的。
- wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间)。
- 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常。
/*** wait使用*/
public class WaitDemo {public static void main(String[] args) {Object lock = new Object();Thread t1 = new Thread(() -> {System.out.println("线程1开始执行");try {synchronized (lock) {System.out.println("线程1调用wait方法....");//无限期等待状态lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1执行完成");},"线程1");t1.start();}
} 
import java.util.concurrent.TimeUnit;public class WaitSleepDemo7 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {synchronized (lock){System.out.println("线程1:开始执行");try{lock.wait(0);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:结束执行");}System.out.println("线程1:终止执行");},"wait0");t1.start();TimeUnit.SECONDS.sleep(1);System.out.println("执行线程1的终止方法");t1.interrupt();}
}
1.1.3.wait() VS wait(long timeout)
1-不同点:wait(long timeout):当线程超过了设置的时间之后,自动恢复执行;而wait():无限等待状态。(0表示无限等待状态)

2-不同点:wait(long timeout):线程会进入WAITING状态;而wait():线程会进入TIMED_WAITING状态。
import java.time.LocalDateTime;public class WaitDemo4 {public static void main(String[] args) {Object lock = new Object();Object lock2 = new Object();new Thread(() -> {System.out.println("线程1:开始执行");synchronized (lock){try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:执行完成");}},"无参wait线程").start();new Thread(() -> {synchronized (lock2){System.out.println("线程2:开始执行 |" + LocalDateTime.now());try {lock2.wait(60 * 60 * 60 * 1000); //1h} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:执行完成 |" + LocalDateTime.now());}},"有参wait线程").start();}
}



3-共同点:无论是wait(long timeout)还是wait(),都会使当前线程进入休眠状态。
4-共同点:无论是wait(long timeout)还是wait(),都能使用notify/notifyAll进行唤醒。
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;public class WaitDemo6 {public static void main(String[] args) {Object lock = new Object();new Thread(() -> {System.out.println("线程1:开始执行");synchronized (lock){try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:执行完成");}},"无参wait线程").start();new Thread(() -> {synchronized (lock){System.out.println("线程2:开始执行 |" + LocalDateTime.now());try {lock.wait(60 * 60 * 60 * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:执行完成 |" + LocalDateTime.now());}},"有参wait线程").start();new Thread(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock){System.out.println("唤醒所有线程");lock.notifyAll();}}).start();}
}

1.1.4.为什么wait要放在Object中?
wait 使用要加锁,也就是要操作锁,锁是针对对象级别的而非线程级别的,线程和对象是⼀对多,所以 wait 最便利的方式是放在 Object 中。
wait(num)和sleep(num):当num>0时,二者执行效果一样。
--->PS:wait(0) 和 sleep(0) 的区别
- wait(0) 表示无期限地等待,直到有线程唤醒它为止。
- Thread.sleep(0) 调用后会让出CPU执行权,让线程稍做休眠,然后重新调度,重新触发一次 CPU 竞争,不管是否竞争到CPU执行权,都会继续执行,直到执行结束。(类似于Thread.yield())
/*** wait(0)和sleep(0)*/ public class WaitSleepDemo7 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Thread t1 = new Thread(() -> {synchronized (lock){System.out.println("线程1:开始执行");try{lock.wait(0);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:执行结束");}},"wait(0)");t1.start();Thread t2 = new Thread(() -> {System.out.println("线程2:开始执行");try {Thread.sleep(0);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:执行结束");},"sleep(0)");t2.start();} }
--->PS:wait 和 sleep 释放锁行为的区别
wait和sleep在有锁的情况下,锁的处理行为是完全不同的:
- wait方法(不管是有参还是无参)在执行时都会释放锁。
- sleep方法不会释放锁。
import java.util.concurrent.TimeUnit;/*** wait和sleep释放锁行为的区别*/ public class WaitSleepDemo8 {public static void main(String[] args) throws InterruptedException {Object lock = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {synchronized (lock){System.out.println("线程1:开始执行");try{lock.wait(3 * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1:结束执行");}},"wait");t1.start();Thread t2 = new Thread(() -> {synchronized (lock2) {System.out.println("线程2:开始执行");try {Thread.sleep(3 * 1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2:结束执行");}},"sleep");t2.start();//创建2个线程,先让线程休眠1秒之后,尝试获取锁,看能不能获取到锁//如果可以获取到锁,说明休眠时线程是释放锁的,而如果获取不到锁,说明是不释放锁的Thread t3 = new Thread(() -> {try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("尝试获取wait方法的锁");synchronized (lock){System.out.println("成功获取wait的锁");}},"wait2");t3.start();Thread t4 = new Thread(() -> {try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("尝试获取sleep方法的锁");synchronized (lock2){System.out.println("成功获取sleep的锁");}},"sleep2");t4.start();} }
为什么wait释放锁而sleep不释放锁?
JVM 强制语法检查,wait 方法默认等待无期限。
--->PS:(常见面试题)wait 和 sleep 的区别(小结)
二者的相同点:
- 都可以让线程休眠。
- 都可以响应 interrupt 中断的请求。
- wait(num) 和 sleep(num):当num>0时,二者执行效果一样。
二者的不同点:
- wait 必须在 synchronized 中使用;而 sleep 却不用。
- wait 是 Object 的方法(属于对象级别);而sleep 是 Thread 的方法(属于线程级别)。
- wait 释放锁;而sleep 不释放锁。
- wait 有可能无限期地等待下去;而sleep 有明确的终止等待时间。 即:wait 可传参,也可不传参;而 sleep必须要传递一个数值类型的参数,否则会报错。
- wait 和 sleep 产生的线程状态是不同的:无参的 wait 是 WAITING 状态;而sleep 是 TIMED_WAITING 状态。
- 一般情况下(响应 interrupt 除过),wait 可以接收一个 notify/notifyAll 之后就继续执行;而 sleep 只能等待超过时间之后再恢复执行。
- wait(0) 表示无期限地等待;而sleep(0)表示重新触发一次 CPU 竞争。
--->PS:3种方法让线程进入休眠/等待状态
- sleep(传参设置休眠时间;不可唤醒)
- TimeUnit(传参设置休眠时间;不可唤醒)
- wait(可传参设置休眠时间,也可不传参无限等待;可以唤醒)
1.2.notify():唤醒当前对象上一个休眠的线程(随机)。
-
⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
-
如果有多个线程等待,则由线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到") (官方是随机的,但具体实现针对不同的JVM是不一样的)

/*** notify使用*/
public class WaitDemo2 {public static void main(String[] args) {Object lock = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {System.out.println("线程1开始执行");try {synchronized (lock) {System.out.println("线程1调用wait方法....");//无限期的等待状态lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1执行完成");},"线程1");Thread t2 = new Thread(() -> {System.out.println("线程2开始执行");try {synchronized (lock) {System.out.println("线程2调用wait方法....");//无限期的等待状态lock.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程2执行完成");},"线程2");Thread t3 = new Thread(() -> {System.out.println("线程3开始执行");try {synchronized (lock2) {System.out.println("线程3调用wait方法....");//无限期的等待状态lock2.wait();}} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程3执行完成");},"线程3");t1.start();t2.start();t3.start();//唤醒lock对象上休眠的线程(随机唤醒一个)Thread t4 = new Thread(() -> {try {Thread.sleep(1500);} catch (InterruptedException e) {}System.out.println("线程4:开始执行,唤醒线程");synchronized (lock) {//发出唤醒通知lock.notify();System.out.println("线程4:执行了唤醒操作");try{Thread.sleep(2000);} catch (InterruptedException e) {}System.out.println("线程4:synchronized执行完了");}}, "线程4");t4.start();}
}

1.3.notifyAll():唤醒当前对象上的所有线程。

/*** notifyAll使用*/
public class WaitDemo3 {public static void main(String[] args) {Object lock = new Object();Object lock2 = new Object();Thread t1 = new Thread(() -> {System.out.println("线程1开始执行");try {synchronized (lock) {System.out.println("线程1调用wait方法....");//无限期的等待状态lock.wait();System.out.println("线程1:恢复执行之后又进入休眠状态");Thread.sleep(2000);System.out.println("线程1执行完成");}} catch (InterruptedException e) {e.printStackTrace();}},"线程1");Thread t2 = new Thread(() -> {System.out.println("线程2开始执行");try {synchronized (lock2) {System.out.println("线程2调用wait方法....");//无限期的等待状态lock2.wait();System.out.println("线程2:恢复执行之后又进入休眠状态");Thread.sleep(2000);System.out.println("线程2执行完成");}} catch (InterruptedException e) {e.printStackTrace();}},"线程2");Thread t3 = new Thread(() -> {System.out.println("线程3开始执行");try {synchronized (lock) {System.out.println("线程3调用wait方法....");//无限期的等待状态lock.wait();System.out.println("线程3:恢复执行之后又进入休眠状态");Thread.sleep(2000);System.out.println("线程3执行完成");}} catch (InterruptedException e) {e.printStackTrace();}},"线程3");t1.start();t2.start();t3.start();//唤醒lock对象上休眠的线程(随机唤醒一个)Thread t4 = new Thread(() -> {try {Thread.sleep(500);} catch (InterruptedException e) {}System.out.println("线程4:开始执行,唤醒线程");synchronized (lock){//发出唤醒通知lock.notifyAll();System.out.println("线程4:执行了唤醒操作");try{Thread.sleep(2000);} catch (InterruptedException e) {}System.out.println("线程4:synchronized执行完了");}}, "线程4");t4.start();}
}

2.注意事项:
①wait/notify/notifyAll必须要配合synchronized一起使用,否则会报错(JVM的强制规定,之所以这样规定,是为了解决线程通讯时执行混乱的问题,synchronized起到约束限制的作用)。

②wait/notify/notifyAll进行synchronized加锁,一定要使用同一个对象进行加锁。
③当调用了notify/notifyAll之后,当前线程不会⻢上释放该对象锁,要等到执⾏notify/notifyAll⽅法的线程将程序执⾏完,也就是退出同步代码块(synchronized)之后才会释放对象锁。【程序也并不会立即恢复执行,而是尝试获取锁,只有得到锁之后才能继续执行。】
④notify/notifyAll可以多次调用,也可以在wait之前调用(是无用功)。
⑤notifyAll 并不是唤醒所有 wait 等待的线程,而是唤醒当前对象处于 wait 等待的所有线程。
相关文章:
JavaWeb12-线程通讯(线程等待和唤醒)
目录 1.方法介绍 1.1.wait()/wait(long timeout):让当前线程进入等待状态。 1.1.1.wait执行流程 1.1.2.wait结束等待的条件 1.1.3.wait() VS wait(long timeout) 1.1.4.为什么wait要放在Object中? --->PS:wait(0) 和 sleep(0) 的区…...
江苏专转本如何事半功倍的备考
专转本如何事半功倍的备考 一个人学习成绩的优劣取决于他的学习能力,学习能力包括三个要素:规范的学习行为;良好的学习习惯;有效的学习方法。有了规范的学习行为才能培养出良好的学习习惯,形成了良好的学习习惯就会形成…...
linux下安装mongoDB
一、下载mongoDB包 下载地址: https://www.mongodb.com/try/download/community 个人建议:如果是学习阶段,使用5以下版本更好些。 二、安装及配置 1、安装 # 1、解压 $ tar -zxvf mongodb-linux-x86_64-rhel70-4.4.19-rc1.tgz# 2、迁移目…...
掌握MySQL分库分表(七)广播表、绑定表实战,水平分库+分表实现及之后的查询和删除操作
文章目录什么是广播表广播表实战数据库配置表Java配置实体类配置文件测试广播表水平分库分表配置文件运行测试什么是绑定表?绑定表实战配置数据库配置Java实体类配置文件运行测试水平分库分表后的查询和删除操作查询操作什么是广播表 指所有的分片数据源中都存在的…...
企业为什么需要数据可视化报表
数据可视化报表是在商业环境、市场环境已经改变之后,发展出来为当前企业提供替代解决办法的重要方案。而且信息化、数字化时代,很多企业已经进行了初步的信息化建设,沉淀了大量业务数据,这些数据作为企业的资产,是需要…...
5个有效的华为(HUAWEI)手机数据恢复方法
5个有效的手机数据恢复方法 华为智能手机中的数据丢失比许多人认为的更为普遍。发生这种类型的丢失有多种不同的原因,因此数据恢复软件的重要性。您永远不知道您的智能手机何时会在这方面垮台;因此,预防总比哀叹好,这就是为什么众…...
【Java并发编程】线程安全(一)Synchronized原理
Synchronized底层实现 简单来说,Synchronized关键字的执行主体是线程对象,加锁是通过一个锁对象来完成的是,而锁对象底层关联了一个c源码的monitor的对象,monitor对象底层又对应了操作系统级别的互斥锁,同一时刻只有一…...
[apollo]vue3.x中apollo的使用
[apollo]vue3.x中apollo的使用通过客户端获取Apollo配置环境工具的安装获取Apollo配置相关代码错误提示Uncaught (in promise) Error: Apollo client with id default not found. Use provideApolloClient() if you are outside of a component setup通过开放接口获取Apollo配置…...
system()函数启用新进程占有原进程的文件描述符表的问题
我在A程序中占用了/dev/video0这个独占模式的设备文件,在A中用system函数启用了B程序,B程序的代码中并不包含对/dev/video0的访问,但是我发现B程序也占用了/dev/video0,并且我在A程序中关闭了/dev/video0后,A程序不再占…...
nignx(安装,正反代理,安装tomcat设置反向代理,ip透传)
1安装nginx 安装wget Yum install -y wget 下载(链接从官网找到右键获取) 以下过程root 安装gcc Yum -y install gcc c 安装pcre Yum install -y pcre pcre-devel Openssl Yum install -y openssl openssl-devel 安装zlib Yum install -y zlib zlib-devel 安装make Yum inst…...
sklearn模块常用内容解析笔记
文章目录 回归模型评价指标R2_score预备知识R2_score计算公式r2_score使用方法注意事项参考文献回归模型评价指标R2_score 回归模型的性能的评价指标主要有:RMSE(平方根误差)、MAE(平均绝对误差)、MSE(平均平方误差)、R2_score。但是当量纲不同时,RMSE、MAE、MSE难以衡量模…...
我的 System Verilog 学习记录(2)
引言 从本文开始,就开始系统学习 System Verilog ,不只是语法,还有结合 Questa Sim 的实际编程练习、Debug。 本文简单介绍 System Verilog 语言的用途以及学习的必要性。 前文链接: 我的 System Verilog 学习记录(…...
【调研报告】Monorepo 和 Multirepo 的风格对比及使用示例
带有权重的Monorepo和Multirepo对比 功能/特性MonorepoMultirepoMonorepo权重值Multirepo权重值代码管理管理多个代码库更加复杂管理单个代码库更加简单37依赖管理可以简化依赖管理依赖冲突可能会更加困难73构建和部署构建和部署更加容易构建和部署可能需要更多的配置82团队协…...
Retrofit源码分析
文章目录一、简介二、源码分析2.1Retrofit的本质流程2.2源码分析2.2.1 创建Retrofit实例步骤1步骤2步骤3步骤4步骤5总结2.2.2创建网络请求接口的实例外观模式 & 代理模式1.外观模式2. 代理模式步骤3步骤4总结2.2.3执行网络请求同步请求OkHttpCall.execute()1.发送请求过程2…...
Mybatis-Plus入门系列(20) -兼容多种数据库
有道无术,术尚可求,有术无道,止于术。 文章目录前言方案分析1. 分页2. XML自定义SQL案例演示1. 配置2. 简单分页查询3. 带方言的分页查询参考前言 在我们实际开发软件产品过程中,数据库的类型可能不是确定的,也有客户…...
JetPack板块—Android X解析
Android Jetpack简述 AndroidX 是Android团队用于在Jetpack中开发,测试,打包,发布和版本管理的开源项目。相比于原来的Android Support库,AndroidX 可以称得上是一次重大的升级改进。 和Support库一样,AndroidX与Android 操作系…...
C++学习笔记-数字
当我们使用数字时,通常我们使用原始数据类型,例如 int,short,long,float 和 double 等。数字数据类型,它们的可能值和取值范围在讨论 C 数据类型时已经解释了。 C 定义数字 我们已经在之前笔记的各种实例…...
Nginx——Nginx的基础原理
摘要 Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器,是一个高性能的HTTP和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器。Nginx 是由俄罗斯人 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,它已经在该站点运行超过两年半了。…...
服务端开发Java之备战秋招面试篇1
在这个面试造火箭工作拧螺丝的时代背景下,感觉不是很好,不过还好也是拿到了还行的offer,准备去实习了,接下来就是边实习边准备秋招了,这半年把(技术栈八股文面经算法题项目)吃透,希望…...
【C++的OpenCV】第三课-OpenCV图像加载和显示
我们开始学习OpenCV一、OpenCV加载图片和显示图片1.1 imread()函数的介绍1.2 cv::namedWindow()函数的介绍1.4 imshow()函数介绍1.5 Mat容器介绍二、 代码实例(带注释)2.1 代码2.2 执行结果一、OpenCV加载图片和显示图片 本章节中,将会学习到…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
【Java学习笔记】BigInteger 和 BigDecimal 类
BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点:传参类型必须是类对象 一、BigInteger 1. 作用:适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...
论文笔记——相干体技术在裂缝预测中的应用研究
目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术:基于互相关的相干体技术(Correlation)第二代相干体技术:基于相似的相干体技术(Semblance)基于多道相似的相干体…...
uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...
企业大模型服务合规指南:深度解析备案与登记制度
伴随AI技术的爆炸式发展,尤其是大模型(LLM)在各行各业的深度应用和整合,企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者,还是积极拥抱AI转型的传统企业,在面向公众…...
6.9-QT模拟计算器
源码: 头文件: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMouseEvent>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);…...



