Java EE:2.多线程-初阶(第五弹):wait 和 notify
目录
Q2:如果在wait过程中有多个其他线程竞争这个锁是不是也可能引发线程安全问题呢?
答疑:那如果有多个线程加了同一个锁对象,也都使用了wait,此时notify会不会唤醒错误呢??
答疑:这种因为随机调度,但是如果每个都有一个notify唤醒,又因为每个wait时间不一样那种,会不会有错误唤醒??
书接上文:Java EE:2.多线程-初阶(第四弹)~
阶段性小结
线程安全问题
1.是啥
一段代码,多线程中,并发执行后,产生Bug
2.原因
1)操作系统对于线程的调度室随机的,抢占式执行[根本]
2)多个线程同时修改同一个变量
3)修改操作不是原子的
4)内存可见性->编译器优化导致
5)指令重排序(后续再说)
3.解决方案
(1)加锁
synchronized(锁对象){//加锁 //需要加锁的代码 }//解锁锁对象:有多个线程针对同一个对象加锁,会产生互斥
死锁问题
一旦代码触发了死锁,此时线程就卡住了
1.原因
1)互斥
2)不可剥夺/不可抢占
3)请求和保持
4)循环等待
2.解决死锁
1)避免锁嵌套=>打破3)
2)约定加锁顺序=>打破4)
闲聊:解决死锁的方案还有很多,比如大学学习操作系统的时候会学到“银行家算法”,期末考试,必有一道大题考这个,但实际开发中,银行家算法,太复杂了,基本用不上
(2)volatile
编译器优化会出Bug
使用这个关键字修饰的变量,就属于“易失”“易变”,必须每次重新读取内存中的数据了
1.volatile不保证原子性
2.JAVA内存模型
7.wait 和 notify
wait:等待
notify:通知
二者在一起协调线程之间的执行逻辑顺序
wait和notify都是Object 的方法,意味着Java中的任意对象都提供了wait和notify
我们程序员是不喜欢随机性的,虽然我们无法直接干预调度器的调度顺序,但是我们可以让后执行的逻辑(线程)等待先执行的逻辑跑完了,通知一下当前线程,让他继续执行
区分一下join和wait
join也是等,join是等待另一个线程彻底执行完,才继续走
wait也是等,wait是等到另一个线程执行notify才继续走(不需要另一个线程执行完)
答疑:锁不也是等待吗?
锁的等待,是不受控制的
你某个线程的代码执行到加锁的逻辑,一定触发等待吗??
不一定!!不确定其他线程是否是“加锁”状态
7.1线程饿死/线程饥饿
举个栗子:
当多个线程竞争一把锁的时候,获取到锁的线程如果释放了,其他是哪个线程拿到锁?
不确定(随机调度)
操作系统的调度是随机的,其他线程都属于在锁上阻塞等待,是阻塞状态
当前这个释放锁的线程,是就绪状态
因此这个线程有很大概率能够再次拿到这个锁~~
ATM机场景演示线程饿死
就好比把每个线程想象成鸟宝宝
CPU就是鸟妈妈
鸟妈妈捉虫给鸟宝宝吃~~
上述场景,就是wait、notify的典型场景
当拿到锁的线程,发现要执行的任务,时机还不成熟的时候,就使用wait阻塞等待
下面使用代码来演示一下👇
package thread; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-21 * Time: 22:09 */ public class Demo23 { public static void main(String[] args) throws InterruptedException { Object object=new Object(); System.out.println("wait 之前"); object.wait(); System.out.println("wait 之后"); } }注意代码中的wait会抛出下面这个异常👇
对于这个异常,我们并不陌生,之前学习sleep和join时也会抛出这个异常
其实,Java标准库中,每个产生阻塞的方法,都会抛出这个异常
意味着随时可能被Interrupt方法给唤醒~~
下面我们来看运行结果👇
原因就在于这行代码👇
object.wait();上述代码做的第一件事,就是先释放object对象对应的锁
但是,能够释放锁的前提是,object对象应该处于加锁状态,才能谈释放~
得加上锁,才能谈释放,这就好比,得先拿到offer才能谈筛选~~
闲聊:
有太多同学,还没有offer,开始先挑起来了~~
有些同学,投简历,只投某个城市得(比如说只投西安)
北京==深圳>上海>广州==杭州>其他
对于“其他”城市来说,无论是机会的数量、还是机会的质量,相比一线城市都是非常差的
因此更好的做法是,你先去大城市,历练历练,有了一定的积累之后,再考虑回老家怎样怎样~~
所以一定要先做到“全国可飞”,先拿到offer再说
答疑:
Q1:同一家公司可以投好几次吗???
一般公司都是有半年以上的冷却时间的,如果在这个公司,面试的时候,面的不上不下的时候,可能人家会给你内部调岗,给你新的面试机会~~
Q2:出国当程序员咋样???听说日本程序员不错
最好的就是去欧美,尤其是美国,机会多、薪资高
放眼全世界,IT领域里面,最牛逼的,就是美国和咱们
同级别的程序员,美国的薪资,是国内的3倍以上
之前就有人在美国上班,把他自己的工作任务外包给国内的程序员来做~~
而且很轻松(加班相对比较少)
东亚地区、东南亚地区、印度……互联网公司,虽然也有不少机会,但是感觉,不一定有国内好~~卷度不比国内低~~
Q3:有些国外公司是弹性上班的,慕了,能进去要求也高吧??
比如说微软、谷歌、META……相比国内是非常轻松的
对于要求,其实难的不只是技术~~都会有英语面试环节~~
提出问题:为啥wait要先释放锁呢??
wait这个等待,最关键的一点:要先释放锁,给其他线程获取锁的机会~~
先加上锁,才能谈释放👇
所以要使用wait方法,就比如给它加上一层锁~~
答疑:
Q1:wait等待过程中会出现安全问题吗?
等待意味着不执行任何逻辑,谈不上线程安不安全,需要等待wait结束
Q2:如果在wait过程中有多个其他线程竞争这个锁是不是也可能引发线程安全问题呢?
其他线程都已经竞争锁了,说明代码中已经使用synchronized进行编码了
使用synchronized是否就一定有线程安全问题/一定没有线程安全问题
都没法一概而论,得看实际得代码是咋写的~~
7.2wait和notify的配合使用
小结一下wait和notify👇
wait做的事情:(搭配synchronized使用,否则抛异常)
①使当前执行代码的线程进行等待(把线程放到等待队列中)
②释放当前的锁
③满足一定条件时被唤醒,重新尝试获取这个锁
wait结束的条件:
①其他线程调用该对象的notify方法
②wait等待时间超时(wait方法提供一个带有timeout参数的版本,来指定等待时间)
③其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedException异常
notify方法是唤醒等待的线程
①方法notify也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其他线程
②如果有多个线程等待,则有线程调度器随机挑选出一个呈wait状态的线程(并没有先来后到)
③在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁
1.针对相同对象
package thread; import java.util.Scanner; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-22 * Time: 10:46 */ public class Demo24 { public static void main(String[] args) { Object locker=new Object(); Thread t1=new Thread(()->{ try{ synchronized (locker){ System.out.println("wait 之前"); locker.wait(); System.out.println("wait 之后"); } }catch (InterruptedException e){ throw new RuntimeException(e); } }); Thread t2=new Thread(()->{ Scanner sc=new Scanner(System.in); System.out.println("输入任意内容,通知唤醒 t1"); sc.next();//此处的next就是一个带有阻塞的操作,等待用户在控制台输入 //这里同样也是,需要先拿到锁,再进行notify(属于Java中给出的限制) //locker.notify(); synchronized (locker){ locker.notify();//Java大佬说加上锁好~咱就加上~ } }); t1.start(); t2.start(); } }注意:wait操作必须要搭配锁来进行,wait会先释放锁
notify操作,原则上说,不涉及加锁解锁的操作
在Java中,也强制要求notify搭配synchronized(其实加不加都行,但是Java大佬说加上比较好,那咱就加上~~)
闲聊:
线程,锁,都是操作系统本身支持的特性
wait和notify在操作系统中,也有原生的对应的API(C语言)
操作系统原生API中,wait必须搭配锁使用,notify则不需要~~
上述代码中需要注意的点👇
wait和notify是针对同一个对象,才能生效的,这个相同的对象,是这俩线程沟通的桥梁~~
如果是两个不同的对象,则没有任何相互影响和作用~~
运行结果如下👇
不能说这边还没wait呢,你就notify~~
在上述代码的过程中,线程一启动wait就执行了,但是notify是在用户输入才执行,而用户操作时间不可能在wait之前(对于整个计算机系统来说,性能瓶颈,最明显的就是屏幕前坐着的这只猴~~)拿代码演示一下👇
所以务必要确保,先wait后notify,才有作用
如果是先notify,后wait,此时wait无法被唤醒(我生君未生,君生我已老~~)
就算“一炮打空了~”,notify这个线程,也没有副作用(notify一个没有在wait的对象,不会抛出异常/其他报错~~)
2.针对不同对象
将上述代码略微调整👇
我们发现无论我们输入什么,t1线程并不会结束,仍然处于阻塞状态👇
上述代码中wait的是locker,但是通知的是locker2,因此并不生效~~
答疑:那如果有多个线程加了同一个锁对象,也都使用了wait,此时notify会不会唤醒错误呢??
下面用代码来演示一下👇
package thread; import java.util.Scanner; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-22 * Time: 11:29 */ public class Demo25 { public static void main(String[] args) { Object locker=new Object(); Thread t1=new Thread(()->{ try{ System.out.println("t1 wait 之前"); synchronized (locker){ locker.wait(); } System.out.println("t1 wait 之后"); }catch (InterruptedException e){ throw new RuntimeException(e); } }); Thread t2=new Thread(()->{ try{ System.out.println("t2 wait 之前"); synchronized (locker){ locker.wait(); } System.out.println("t2 wait 之后"); }catch (InterruptedException e){ throw new RuntimeException(e); } }); Thread t3=new Thread(()->{ Scanner sc=new Scanner(System.in); System.out.println("输入任意内容,通知唤醒线程"); sc.next(); synchronized (locker){ locker.notify(); } }); t1.start(); t2.start(); t3.start(); } }
因此,如果有多个线程在同一个对象上wait,进行notify的时候是随机唤醒其中一个线程,也是取决于调度器的实现
一次notify唤醒一个wait,多个notify就唤醒多个wait👇
闲聊:
IDEA中快捷键:
Shift+F10:运行上次的程序
Ctrl+Shift+F10:运行当前打开的程序
此时就能够全部唤醒👇
答疑:这种因为随机调度,但是如果每个都有一个notify唤醒,又因为每个wait时间不一样那种,会不会有错误唤醒??
这种情况是会有的
实际上同一个对象wait多次这种情况,一般这些wait的线程都是干同样的工作的,唤醒谁,其实都一样~~
就好比:比特给同学们准备模拟面试,比特这边就需要有一批老师,负责模拟面试,每次有一个同学来了,随机挑一个老师,由于所有的老师都是统一培训过的,因此面试的难度/标准/要求都一致
7.3notifyAll方法
notifyAll():一次唤醒所有等待线程
将上述代码稍稍调整👇
我们发现t1和t2两个wait的线程,一次性被唤醒了👇
理解notify和notifyAll
notify只唤醒等待队列中的一个线程,其他线程还是乖乖等着
notifyAll是一下子全都唤醒,需要这些线程重新竞争锁
下面来仔细思考这个过程👇(其实还存在锁竞争的事情~)
上述过程虽然看起来是同时唤醒了t1和t2,但实际上由于wait唤醒之后,要重新加锁,因此:
其中某个线程,先加上锁,开始执行
另一个线程因为加锁失败,再次阻塞等待
等到先走的线程解锁了,后走的线程才能加上锁,继续执行~~
闲聊:
1.对于蓝桥杯问题
面试中还是有点含金量的,但是省奖就比较水了,最好还是国奖,但其实在面试管眼里,最重要的还是实习经历,这个即使是蓝桥杯国奖也不能与之相比的
因为公司招聘,是为了你能进去干活~~
不是说你能达到什么样的技术水平,就一定能干活,而是经过其他公司的历练~~
很多事情,不是技术问题~~
2.大概什么时候找实习?
只要能做出简历,就可以找了,大部分同学是大三找的,也有大二找的,如果是大一的话,就不着急了,先好好感受美好的大学生活
3.秋招是大三开学还是大四开学?
大四开学
4.工作中的代码是不是很难?
非常简单,有句话是“面试造火箭,工作拧螺丝”,但这个前提还是你要熟悉公司的业务
5.啥是业务??
简单说就是公司是干啥的~~
百度=>搜索
阿里=>电商
腾讯=>通讯
比特=>职业化教育
比如说实现搜索功能,搜索功能该咋做??
再比如说比特的职业化教育,有直播课、录播课、相关服务…
7.4wait、join和sleep
wait和join类似,也是提供了“死等”版本和“超时时间”版本
locker.wait();//死等 locker.wait(10000);//最多等待10s,还没有notify,也不等了~wait引入超时时间之后,直观看起来,就和sleep很像:
wait有等待时间
sleep有等待时间
wait可以用notify提前唤醒
sleep可以用Interrupt提前唤醒
wait和sleep最主要的区别:针对锁的操作(面试题)
1)wait必须要搭配锁,先加锁,才能用wait,而sleep不需要
2)如果都是在synchronized内部使用,wait会释放锁,sleep不会释放锁~
synchronized(locker){ Thread.sleep(1000);//相当于“抱着锁睡着了~~” } //其他线程也是没法获取到这个锁的,直接“与世隔绝”了~~3)wait是Object的方法,sleep是Thread的静态方法
闲聊:
学习阶段,会用很多sleep
真正开发,很少用sleep,因为使用sleep一般就是在纯纯的浪费时间
答疑:wait也会参与CPU调度吗?
wait是阻塞,不参与CPU调度,sleep也不参与CPU调度
wait就是咱们可以随时唤醒,但是sleep就不能随时唤醒(Interrupt看起来是唤醒sleep,其实本身的作用是通知线程终止)
7.5课后作业题
题目:有三个线程,分别只能打印A、B和C,要求按顺序打印ABC,打印10次
我可以借助wait和notify来实现上述效果,来编排各个线程的执行顺序👇
package thread; /** * Created with IntelliJ IDEA. * Description: * User: CoderYanger * Date: 2026-05-22 * Time: 16:10 */ public class Demo26 { public static void main(String[] args) throws InterruptedException { Object locker1=new Object(); Object locker2=new Object(); Object locker3=new Object(); Thread t1=new Thread(()->{ try{ for(int i=0;i<10;i++){ synchronized (locker1){ locker1.wait(); } System.out.print("A"); //打印完A去通知locker2,把locker2给唤醒 synchronized (locker2){ locker2.notify(); } } }catch (InterruptedException e){ throw new RuntimeException(); } }); Thread t2=new Thread(()->{ try{ for(int i=0;i<10;i++){ synchronized (locker2){ locker2.wait(); } System.out.print("B"); //打印完B去通知locker3,把locker3给唤醒 synchronized (locker3){ locker3.notify(); } } }catch (InterruptedException e){ throw new RuntimeException(); } }); Thread t3=new Thread(()->{ try{ for(int i=0;i<10;i++){ synchronized (locker3){ locker3.wait(); } System.out.println("C"); //打印完C去通知locker3,把locker3给唤醒 synchronized (locker1){ locker1.notify(); } } }catch (InterruptedException e){ throw new RuntimeException(); } }); t1.start(); t2.start(); t3.start(); //上述逻辑能够完成闭环循环了,但是还需要我们“推一把”,才能启动 //需要确保上述三个线程都执行到wait,再进行notify Thread.sleep(1000); //主线程中,先通知一次locker1,让上述逻辑从t1开始执行 synchronized (locker1){ locker1.notify(); } } }
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐






















所有评论(0)