Java多线程编程—wait/notify机制
文章目录
- 1. 不使用wait/notify机制通信的缺点
- 2. 什么是wait/notify机制
- 3. wait/notify机制原理
- 4. wait/notify方法的基本用法
- 5. 线程状态的切换
- 6. interrupt()遇到方法wait()
- 7. notify/notifyAll方法
- 8. wait(long)介绍
- 9. 生产者/消费者模式
- 10. 管道机制
- 11. 利用wait/notify实现交叉备份
- 12. 方法Sleep()和wait()的区别
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理时不能成为一体的。线程间的通信就是使线程称为整体的必用方案之一,可以说,使线程之间进行通信后系统之间的交互性会更大,CPU利用效率得以大幅提升,同时程序员在处理过程中可以有效把控于监督各线程任务。
1. 不使用wait/notify机制通信的缺点
下面模拟不使用wait/notify机制进行通信,然后分析其缺点所在
public class multithreadingtest {static volatile private List list=new ArrayList() ; //TODO 使用volatile修饰,可以实现线程之间数据的可见性public static void main(String[] args) {Thread thread1=new Thread(new Runnable() { //TODO 线程thread1实现循环向list中添加元素@Overridepublic void run() {int i=1;while(i>0){list.add(i); //TODO 向集合中添加元素System.out.println("添加了"+i+"个元素");i++;try {Thread.sleep(1000); //TODO 这里睡眠是为了等待Thread2执行} catch (InterruptedException e) {e.printStackTrace();}}}});Thread thread2=new Thread(new Runnable() { //TODO 线程thread2判读集合中元素中的数量是否达到了5@Overridepublic void run() {try {while(true){if(list.size()==5){System.out.println("thread2要退出了");throw new InterruptedException(); //TODO 以抛出异常的方式结束线程}}}catch (InterruptedException e){e.printStackTrace();}}});thread1.setName("thread1");thread2.setName("thread2"); //TODO 设置进程名thread1.start();thread2.start();}}

分析代码,这里的线程间通信是指,thread1向集合中添加的数据达到5个后,通知线程2结束。虽然两个线程以集合为媒介完成了通信,但还存在缺点,thread2需要使用while循环不断轮询检测某一个条件,这样会很浪费CPU资源,所以需要引入一种机制,在减少CPU资源浪费的同时,还可以实现在多个线程之间的通信,这就是wait/notify机制。
2. 什么是wait/notify机制
wait/notify机制是Java中用于线程间通信的一种机制。它基于“监视器对象”(monitor object)的概念,通过在多个线程之间共享一个对象锁来实现线程的协作。在wait/notify机制中,线程可以调用对象的wait()方法来阻塞自己,直到其他线程调用同一个对象的notify()或notifyAll()方法来唤醒它。当一个线程调用wait()方法时,它会释放对象的锁,使其他线程能够获取该对象的锁并执行相应的操作。当其他线程调用notify()或notifyAll()方法时,它们会唤醒等待在该对象上的线程,使它们重新竞争对象的锁,继续执行操作。使用wait/notify机制可以实现一些复杂的线程协作模式,比如生产者消费者模式、读写锁模式等。但是在使用过程中需要特别注意避免死锁、竞态条件等问题。
3. wait/notify机制原理
注意:拥有相同锁的线程才可以实现wait/notify机制。
wait()方法
wait()Object类的方法,它的作用是使当前执行该方法的进程进行等待,在wait()所在的代码处暂停执行,并释放锁
- 在调用wait之前,该线程必须要获得该对象的对象级别锁
- 如果wait之前没有持有适当的锁,抛出
IllegalMonitorStateException - 通过通知机制使得某个线程继续执行wait方法后面的代码,对线程的选择是按wait方法的顺序确定的(可能多个线程wait的情况)
notify()方法
该方法用于唤醒当前正在wait的线程,对线程的选择是按wait方法的顺序确定的
- 调用notigy方法必须是在同步方法或同步块中调用,即调用之前也要获得锁,否则抛出
IllegalMonitorStateException - 执行notify方法后,当前线程不会立即释放该锁,而是等到执行notify方法的线程将同步区代码全部执行完毕后才释放锁
4. wait/notify方法的基本用法
wait()方法使当前线程暂停运行,并释放锁
- 模拟一:模拟未获取锁执行wait方法
public static void main(String[] args) throws InterruptedException {String name=new String();name.wait();}

2. 模拟二:wait()方法的正确使用
public static void main(String[] args) throws InterruptedException {String name=new String();synchronized (name){System.out.println("wait前");name.wait();System.out.println("wait后");}}

3. 模拟三:wait/notify机制的使用
public static void main(String[] args) throws InterruptedException {Thread thread1=new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {System.out.println("thread1跑起来了");try {list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("thread1跑完了");}}});Thread thread2=new Thread(new Runnable() {@Overridepublic void run() {synchronized (list){System.out.println("thread2跑起来了");list.notify();System.out.println("thread2跑完了");}}});thread1.start();Thread.sleep(3000);thread2.start();}

除了notify()方法外还有一个notifyAll()方法,该方法执行后会按照执行wait()方法的倒序依次唤醒“全部”的线程
5. 线程状态的切换
Thread中存在着很多的方法可以改变线程对象的状态

- 创建一个新的线程对象后,再调用它的start()方法,系统会为该线程分配CPU资源,处于可运行状态(就绪状态),这个一个准备运行的状态,如果线程抢占倒cpu资源就会转移到运行状态
- 可运行状态和运行状态是可以相互切换的。运行状态的线程可能会被高优先级的线程抢占资源从而进入就绪态
- 线程进入可运行状态的情况
- 调用sleep方法超过指定休眠时间
- 线程获得了同步监视器(锁)
- 线程在等待某个通知,而相应的线程发送了通知
- 处于挂起(等待)状态的线程调用了resume方法
- 暂停(等待)状态结束后,线程进入可运行状态等待系统分配资源,出现阻塞的情况大体分为5种
- 线程调用Sleep方法,主动放弃占用的处理器资源
- 线程调用了阻塞式I/O方法(即线程忙着处理I/O操作了),在该方法返回前,线程被阻塞
- 线程尝试获取某个对象的锁,但锁被占用
- 线程等待某个通知
- 程序调用了suspend方法将该线程挂起,此方法容易导致死锁,尽量避免
- run方法执行完毕后进入线程销毁阶段,整个线程执行完毕
Sleep()方法和wait()方法一样也会让线程陷入阻塞,但sleep并不会让持有锁的线程释放锁,而锁陷入等待(这是一种同步效果)
6. interrupt()遇到方法wait()
当线程调用wait()方法后,再对该线程对象执行interrupt()方法时,会出现InterruptedException异常
public static void main(String[] args) throws InterruptedException {Thread thread1=new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {System.out.println("thread1跑起来了");try {list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("thread1跑完了");}}});thread1.start();thread1.interrupt();}

7. notify/notifyAll方法
每次调用notify方法,只通知一个线程进行唤醒,唤醒的顺序按执行wait()方法的正序。
public static void main(String[] args) throws InterruptedException {Thread thread1=new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {System.out.println("第一个wait");try {list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}list.notify();System.out.println("第一个结束");}}});Thread thread2=new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {System.out.println("第二个wait");try {list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}list.notify();System.out.println("第二个结束");}}});Thread thread3=new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {System.out.println("第三个wait");try {list.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}list.notify();System.out.println("第三个结束");}}});Thread thread4=new Thread(new Runnable() {@Overridepublic void run() {synchronized (list){System.out.println("开始通知");list.notify();System.out.println("通知结束");}}});thread1.start();thread2.start();thread3.start();Thread.sleep(3000);thread4.start();}

总结
- 执行notify方法后,按照wait执行顺序唤醒其它进行
- 执行同步代码块(临界区)过程中,遇到异常而导致线程终止时,锁也会被释放
- 执行临界区代码中执行了锁所属对象的wait方法,线程会释放对象锁,等待被唤醒
nofity一次调用只能唤醒一个线程,所以容易出现部分线程对象没有被唤醒的情况。为了唤醒全部线程,可以使用notifyAll方法,它会按照执行wait方法的倒序一次对线程进行唤醒的。
8. wait(long)介绍
带有一个参数的wait(long)方法的功能是等待某一个时间内是否有线程进行通知唤醒,如果超过这个时间则自动唤醒。能继续向下运行的前提是再次持有锁。(注意也是可以在long时间内被其它线程唤醒的)
public static void main(String[] args) throws InterruptedException {Thread thread1=new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {System.out.println("开始时间:"+System.currentTimeMillis());try {list.wait(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("开始时间:"+System.currentTimeMillis());}}});thread1.start();}

9. 生产者/消费者模式
生产者/消费者模式:在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品。wait/notify机制最经典的案例“生产者与消费者模式”模式。

案例一:单生产者和单消费者模式
一个生产者生产,一个消费者消费,没有产品时生产者才能生产,有产品时消费者才能消费
//消费者
public class P {private String lock;public P(String lock){super();this.lock=lock;}public void GetValue(){try{synchronized (lock){if(ValueObject.value.equals("")){ //value不为空说明消费者还没有消费,所以生产者需要等待lock.wait();}System.out.println("我现在要开始消费了!");lock.notify();System.out.println("消费的产品:"+ValueObject.value);ValueObject.value="";}} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
//生产者
public class C {private String lock;public C(String lock){super();this.lock=lock;}public void SetValue(){try{synchronized (lock){if(!ValueObject.value.equals("")){ //value不为空说明消费者还没有消费,所以生产者需要等待lock.wait();}System.out.println("我现在要开始生产了!");lock.notify();String value= String.valueOf(System.currentTimeMillis())+"_"+String.valueOf(System.nanoTime());System.out.println("生产的产品:"+value);ValueObject.value=value;}} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
//消费者线程
public class ThreadB extends Thread{private P p;public ThreadB(P p){super();this.p=p;}@Overridepublic void run(){while(true){ //消费者消费p.GetValue();}}
}
//生产者线程
public class ThreadA extends Thread{private C c;public ThreadA(C c){super();this.c=c;}@Overridepublic void run(){while(true){ //生产者不断生产c.GetValue();}}
}
//main函数public static void main(String[] args) {String lock=new String("");P p=new P(lock);C c=new C(lock);ThreadA threadA=new ThreadA(p);ThreadB threadB=new ThreadB(c);threadA.start();threadB.start();}

案例二:多生产者和多消费者模式的问题
在上面案例代码的基础上,多增加生产者,使得生产者的数据远远大于消费者,会出现连续生产的问题,导致生产的内容会发生覆盖,而消费者的数量远远大于生产者的数量时,会发生消费者消费空值的情况。原因是if条件发生改变时,其它的线程并不知道。同时还会出现唤醒同类的情况,最终出现连续生产或连续消费,导致程序的逻辑出现错误。解决这个问题的方法就是把if判断改为while判断。
写覆盖代码
public class multithreadingtest {public static void main(String[] args) {String lock=new String("");P p=new P(lock);C c=new C(lock);ThreadA[] pThread=new ThreadA[20];ThreadB[] rThread=new ThreadB[20];for (int i = 0; i < 20; i++) {pThread[i]=new ThreadA(c);pThread[i].setName("生产者"+(i+1));pThread[i].start();}for (int i = 0; i < 2; i++) {rThread[i]=new ThreadB(p);rThread[i].setName("消费者"+(i+1));rThread[i].start();}}
}

消费空值代码
public class multithreadingtest {public static void main(String[] args) {String lock=new String("");P p=new P(lock);C c=new C(lock);ThreadA[] pThread=new ThreadA[20];ThreadB[] rThread=new ThreadB[20];for (int i = 0; i < 2; i++) {pThread[i]=new ThreadA(c);pThread[i].setName("生产者"+(i+1));pThread[i].start();}for (int i = 0; i < 20; i++) {rThread[i]=new ThreadB(p);rThread[i].setName("消费者"+(i+1));rThread[i].start();}}
}

将多生产多消费情况下的if判断条件改为while,就不会出现连续生产和连续消费问题,即使唤醒同类线程还是会执行while判断条件,如果条件为true则执行wait方法,避免了连续生产和连续消费的情况,但程序运行过程中却出现了假死,也就是所有的线程都呈现等待状态。“假死”的现象就是线程进入等待状态,如果全部线程都进入了等待状态,程序就不执行任何业务功能了,整个项目呈现等待状态。

上面的情况用一种简单情形解释:老爸老妈都会做饭,你和你哥都要吃饭。老妈做完饭通知你和你哥吃饭然后去休息(释放锁开始等待),老爸一看饭做好了,不用他去做了就去休息了(生产者唤醒生产者线程),你哥还是下班的路上,你先去吃饭,结果吃完了,然后还没心没肺的通知哥回来吃饭(消费者线程唤醒消费者线程),你哥到家后一看没有饭,也去休息了,这样所有的线程都休息了,所以出现了假死。假死出现的极大可能原因是连续唤醒了同类。解决假死的方法就是将notify通知换为notifyAll通知,这样就能顺利解决假死问题。
10. 管道机制
管道机制是线程之间消息传递的一种机制,Java语言提供了各种各样的输入输出流,使我们能够很方便地操作数据,其中管道流是一种特殊的流,用于在不同线程间之间传送数据。一个线程发送数据输出到管道,另一个线程从输入管道中读数据,通过使用管道,实现不同线程间的通信,而无须借助与临时文件之类的东西。
测试一:在管道中传递字节流
//输入管道
public class wirteMethod {public void writMethod(PipedOutputStream out) //参数为管道{try {System.out.println("wirte:");for (int i = 0; i < 300; i++) {String outData = "" + (i + 1);out.write(outData.getBytes()); //向管道中写System.out.print("~~~"+outData);}System.out.println();out.close();} catch (IOException e) {throw new RuntimeException(e);}}
}
//输出管道
public class ReadDate {public void readMethod(PipedInputStream input){try{System.out.println("read:");byte[] byteArray=new byte[20];int readLength=input.read(byteArray);while (readLength!=-1){String newData=new String(byteArray,0,readLength);System.out.print("____"+newData);readLength=input.read(byteArray);}System.out.println();input.close();} catch (IOException e) {throw new RuntimeException(e);}}
}
//输入线程
public class ThreadA extends Thread{private wirteMethod wirteMethod;private PipedOutputStream pipedOutputStream;public ThreadA(wirteMethod wirteMethod,PipedOutputStream pipedOutputStream){super();this.wirteMethod=wirteMethod;this.pipedOutputStream=pipedOutputStream;}@Overridepublic void run(){wirteMethod.writMethod(pipedOutputStream);}
}
//输出线程
public class ThreadB extends Thread{private ReadDate readDate;private PipedInputStream pipedInputStream;public ThreadB(ReadDate readDate,PipedInputStream pipedInputStream){super();this.readDate=readDate;this.pipedInputStream=pipedInputStream;}@Overridepublic void run(){readDate.readMethod(pipedInputStream);}
}
//测试
public class multithreadingtest {public static void main(String[] args) throws IOException, InterruptedException {wirteMethod wirteMethod=new wirteMethod();ReadDate readDate=new ReadDate();PipedInputStream inputStream=new PipedInputStream();PipedOutputStream outputStream=new PipedOutputStream();inputStream.connect(outputStream);outputStream.connect(inputStream); //使两个管道之间产生通信链接ThreadB readThread=new ThreadB(readDate,inputStream); //读线程先准备readThread.setName("read");readThread.start();Thread.sleep(2000);ThreadA wirteThread=new ThreadA(wirteMethod,outputStream);wirteThread.setName("witte");wirteThread.start();}
}

从输出结果可以看出,首先是读线程启动,由于当前管道中没有数据被写入,所以线程阻塞,知道有数据写入才继续向下运行
测试二:在管道中传递字符流(基本同上,这里就不演示了)
11. 利用wait/notify实现交叉备份
利用wati/notify机制实现交叉备份:创建20个线程,其中10个线程是向数据库A中备份数据,另外10个线程向数据库B中备份数据,要满足备份工作是交叉进行的(往A备份,然后下次往B备份,轮流交叉),重点是如何利用wait/notify让20个线程变得有序
//备份数据的代码。
public class DBTools {volatile private boolean prevIsa=false; //信号量synchronized public void backupA(){ //A备份try {while(prevIsa==true){ //prevIsa为true,此时则等待wait();}for (int i = 0; i < 5; i++) { //备份五次System.out.println("&&&&&&&&&");}prevIsa=true;notifyAll();} catch (InterruptedException e) {throw new RuntimeException(e);}}synchronized public void backupB(){try { while(prevIsa==false){ wait();}for (int i = 0; i < 5; i++) {System.out.println("*********");}prevIsa=false;notifyAll();} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
//a备份线程
public class ThreadA extends Thread{private DBTools dbTools;public ThreadA(DBTools dbTools){super();this.dbTools=dbTools;}@Overridepublic void run(){dbTools.backupA();}
}
//b备份线程
public class ThreadB extends Thread{private DBTools dbTools;public ThreadB(DBTools dbTools){super();this.dbTools=dbTools;}@Overridepublic void run(){dbTools.backupB();}
}
//测试代码
public class multithreadingtest {public static void main(String[] args) throws IOException, InterruptedException {DBTools dbTools=new DBTools();for (int i = 0; i < 20; i++) {ThreadA threadA=new ThreadA(dbTools);threadA.start();ThreadB threadB=new ThreadB(dbTools);threadB.start();}}
}

交替打印的效果说明AB是交替进行备份的
12. 方法Sleep()和wait()的区别
方法sleep()和Wait()的区别
(1) sleep()是Thread类中的方法,二wait()是Object中的方法
(2) sleep()可以不结合Sysnchronized使用 ,而wait()必须结合
(3) sleep()在执行时不会释放锁,而wait()在执行后锁被释放
(4) sleep()方法执行后线程的状态时TIMED_WAITING,wait()方法执行后线程的状态是等待
相关文章:
Java多线程编程—wait/notify机制
文章目录1. 不使用wait/notify机制通信的缺点2. 什么是wait/notify机制3. wait/notify机制原理4. wait/notify方法的基本用法5. 线程状态的切换6. interrupt()遇到方法wait()7. notify/notifyAll方法8. wait(long)介绍9. 生产者/消费者模式10. 管道机制11. 利用wait/notify实现…...
Three.js教程:旋转动画、requestAnimationFrame周期性渲染
推荐:将NSDT场景编辑器加入你3D工具链其他工具系列:NSDT简石数字孪生基于WebGL技术开发在线游戏、商品展示、室内漫游往往都会涉及到动画,初步了解three.js可以做什么,深入讲解three.js动画之前,本节课先制作一个简单的…...
租车自驾app开发有什么作用?租车便利出行APP开发
在线租车APP有哪些优势,租车APP开发的基本功能,租车自驾app开发有什么作用?租车便利出行APP开发,租车服务平台小程序有哪些功能,租车软件开发需要多少钱,租车app都有哪些,租车平台定制开发,租车…...
linux shell 文件分割
split 按照 10m 大小进行分割 split -b 10m large_file.bin new_file_prefix...
智慧农业系统开发功能有哪些?
农业从古至今都是备受关注的话题,新时代背景下农业发展已经融合了互联网,数字化技术等新型发展方式,形成了农业物联网管控系统,让农业生产更加科技化、智能化、高效化,对农业可持续发展有巨大的推动作用。所以…...
【C语言】 指针的进阶 看这一篇就够了
目录 1.字符指针 2.数组指针 3.指针数组 4.数组传参和指针传参 5.函数指针 6.函数指针数组 7.指向函数指针数组的指针 8.回调函数 9.qsort排序和冒泡排序 1.字符指针 让我们一起来回顾一下指针的概念! 1.指针就是一个变量,用来存放地址,地址…...
redis set list
Listlist: 插入命令:lpush / rpush 查看list列表所有数据(-1 表示最后一个):lrange key 0 -1 查看列表长度(key 不存在则长度返回0 ): llen key list长度 获取下表 为 0 的元素 修改下标为0的元素,改为haha 移除列表的第一个元素 或最后一…...
如何解决DNS劫持
随着互联网的不断发展,DNS(域名系统)成为了构建网络基础的重要组成部分。而DNS遭到劫持,成为一种常见的安全问题。那么DNS遭到劫持是什么意思呢?如何解决DNS劫持问题呢?下面就让小编来为您一一解答。 DNS遭到劫持是什么意思? DNS遭到劫持指的是黑客通…...
【LeetCode】剑指 Offer(28)
目录 题目:剑指 Offer 54. 二叉搜索树的第k大节点 - 力扣(Leetcode) 题目的接口: 解题思路: 代码: 过啦!!! 题目:剑指 Offer 55 - I. 二叉树的深度 - 力…...
「ML 实践篇」模型训练
在训练不同机器学习算法模型时,遇到的各类训练算法大多对用户都是一个黑匣子,而理解它们实际怎么工作,对用户是很有帮助的; 快速定位到合适的模型与正确的训练算法,找到一套适当的超参数等;更高效的执行错…...
域名解析协议-DNS
DNS(Domain Name System)是互联网上非常重要的一项服务,我们每天上网都要依靠大量的DNS服务。在Internet上,用户更容易记住的是域名,但是网络中的计算机的互相访问是通过 IP 地址实现的。DNS 最常用的功能是给用户提供…...
分享:包括 AI 绘画在内的超齐全免费可用的API 大全
AI 绘画已经火出圈了,你还不知道哪里可以用嘛?我给大家整理了超级齐全的免费可用 API,包括 AI 绘画在内,有需要的小伙伴赶紧收藏了。 AI 绘画/AI 作画 类 AI 绘画:通过AI 生成图片,包括图生文、文生图等。…...
虹科新闻 | 虹科与Overland-Tandberg正式建立合作伙伴关系
虹科Overland-Tandberg 近日,虹科与美国Overland-Tandberg公司达成战略合作,虹科正式成为Overland-Tandberg公司在中国区域的认证授权代理商。未来,虹科将携手Overland-Tandberg,共同致力于提供企业数据管理和保护解决方案。 虹科…...
架构设计三原则
作为程序员,很多人都希望成为一名架构师,但并非简单地通过编程技能就能够达成这一目标。事实上,优秀的程序员和架构师之间存在一个明显的鸿沟——不确定性。 编程的本质是确定性的,也就是说,对于同一段代码,…...
Android 性能优化——ANR监控与解决
作者:Drummor 1 哪来的ANR ANR(Application Not responding):如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR) 错误。如果应用位于前台,系统会向用户显示一个对话框。ANR 对话框会为用户提供强制退出应用的选项…...
Machine Learning-Ex3(吴恩达课后习题)Multi-class Classification and Neural Networks
目录 1. Multi-class Classification 1.1 Dataset 1.2 Visualizing the data 1.3 Vectorizing Logistic Regression 1.3.1 Vectorizing the cost function(no regularization) 1.3.2 Vectorizing the gradient(no regularization&#…...
【Java】SpringBoot事务回滚规则
SpringBoot事务回滚规则SpringBoot事务回滚规则SpringBoot事务回滚规则 在SpringBoot中,如果一个方法被声明为Transactional,则会开启一个事务。如果这个方法中的任何一个步骤失败了(比如抛出了异常),则该事务将会回滚…...
使用cocopod就那么容易
第一节、配置coopod 打开终端替换ruby镜像源,系统自带的镜像源(gem sources --remove https://rubygems.org/)被墙挡住了或者(https://ruby.taobao.org/)已过期。需替换成新的镜像源。 1).先查看已有的镜像是否是:ht…...
第14届蓝桥杯C++B组省赛
文章目录A. 日期统计B. 01 串的熵C. 冶炼金属D. 飞机降落E. 接龙数列F. 岛屿个数G. 子串简写H. 整数删除I. 景区导游J. 砍树今年比去年难好多 Update 2023.4.10 反转了,炼金二分没写错,可以AC了 Update 2023.4.9 rnm退钱,把简单的都放后面…...
面向对象编程(进阶)3:方法的重写
目录 3.1 方法重写举例 Override使用说明: 3.2 方法重写的要求 3.3 小结:方法的重载与重写 (1)同一个类中 (2)父子类中 3.4 练习 父类的所有方法子类都会继承,但是当某个方法被继承到子类…...
关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
pycharm 设置环境出错
pycharm 设置环境出错 pycharm 新建项目,设置虚拟环境,出错 pycharm 出错 Cannot open Local Failed to start [powershell.exe, -NoExit, -ExecutionPolicy, Bypass, -File, C:\Program Files\JetBrains\PyCharm 2024.1.3\plugins\terminal\shell-int…...
云原生安全实战:API网关Envoy的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关 作为微服务架构的统一入口,负责路由转发、安全控制、流量管理等核心功能。 2. Envoy 由Lyft开源的高性能云原生…...
【Zephyr 系列 16】构建 BLE + LoRa 协同通信系统:网关转发与混合调度实战
🧠关键词:Zephyr、BLE、LoRa、混合通信、事件驱动、网关中继、低功耗调度 📌面向读者:希望将 BLE 和 LoRa 结合应用于资产追踪、环境监测、远程数据采集等场景的开发者 📊篇幅预计:5300+ 字 🧭 背景与需求 在许多 IoT 项目中,单一通信方式往往难以兼顾近场数据采集…...
