✨个人主页:bit me👇
✨当前专栏:Java EE初阶👇
✨每日一语:阅己,越己,悦己;自行,自省,自醒;无味,无谓,无畏。
目 录
⏰一. wait 和 notify 的引入
由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.
wait 和 notify 相比于 join 能更好的控制线程之间的执行顺序
- wait (等待):让当前线程进入等待状态
- notify (通知/唤醒):唤醒对应的 wait 线程。(从阻塞状态唤醒到就绪状态)
注意: wait, notify都是 Object 类的方法.
- 使用 o1.notify() 就可以唤醒调用 o1.wait 的线程!!
- 使用 o2.notify() 是不可以唤醒调用 o1.wait 的线程!
⏲二. wait()方法和notify()方法
对于 wait 来说,内部的执行过程还有点小麻烦
- 释放锁
wait 一上来就得释放锁,,得在调用 wait 之前,先拿到锁,
wait 必须要放到 synchronized 中使用,synchronized 加锁的对象必须是和调用 wait 方法的对象是同一个对象,还得和调用 notify 的对象是同一个对象!!!
-
等待通知
-
当通知到达之后,就会被唤醒,并且尝试重新获取锁
wait 结束等待的条件:
- 其他线程调用该对象的 notify 方法.
- wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
- 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常
代码示例: 观察wait()方法使用
public class Demo17 { public static void main(String[] args) throws InterruptedException { Object object = new Object(); synchronized (object) { System.out.println("wait 之前"); object.wait(); System.out.println("wait 之后"); } }}
notify 方法是唤醒等待的线程
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
- 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
- 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
举例实现 wait 和 notify 的内部执行过程:创建两个线程 一个线程调用 wait,一个线程调用 notify
public class Demo18 {//这个对象用来做锁对象public static Object locker = new Object();public static void main(String[] args) { //用来取等待 Thread waitTask = new Thread(()->{ synchronized (locker) { try { System.out.println("wait 开始"); locker.wait(); System.out.println("wait 结束"); } catch (InterruptedException e) { e.printStackTrace(); } } }); waitTask.start(); //创建一个用来通知 / 唤醒的线程 Thread notifyTask = new Thread(()->{ //让用户来控制,用户输入某个内容之后,再执行通知 Scanner scanner = new Scanner(System.in); System.out.println("输入任意内容,开始通知:"); //next 会阻塞,直到用户真正输入内容以后 scanner.next(); synchronized (locker){ System.out.println("notify 开始"); locker.notify(); System.out.println("notify 结束"); } }); notifyTask.start();}}
执行过程及结果:
线程 1 需要先计算一个结果,线程 2 来使用这个结果,线程 2 就可以 wait ,线程 1 计算完结果之后,notify ,唤醒线程 2。
wait 和 notify 机制,还能有效避免 "线程饿死"。
(有些情况下,调度器可能分配不均匀,导致有些线程反复占用 CPU,有些线程始终捞不着 CPU)
例如:
多个线程抢占一个锁,一个线程 A 抢到了,其他线程排队等待,但是抢到锁的线程 A 却无法完成任务,占着锁不放,导致其他锁也不能干活儿,于是这个线程 A 决定释放锁出来,在外面等待,给其他的锁一些机会,由于线程之间在系统里的调度是随机的,可能下次又是线程 A 进来了,于是多次情况下都是线程 A 占用了 CPU,其他线程都碰不到,导致线程饿死,旱的旱死,涝的涝死。
因此使用 wait 和 notify 就可以解决上述问题,让后面的多个线程拿到锁之后判定当前任务是否可以执行,如果能就干活,不能就进行 wait !wait 来等待合适的时候(条件满足)再继续执行 / 再参与竞争锁
拓展:
- 线程进入了 WAITING 状态,则务必要其他线程来主动唤醒
- 线程进入了 BLOCKED 状态,这个则是其他线程把锁释放之后,操作系统来负责唤醒
- 线程进入了 TIMED_WAITING 状态,操作系统会计时,时间到了之后进行唤醒
这三个阻塞的状态,都会导致 PCB 进入内核队列中对应的阻塞队列。操作系统内核是不区分这三个的状态的!这三个状态是 JVM 中的状态,操作系统内核中衡量线程的状态是另外的方式,没有 JVM 里面的这么细。
notify方法只是一次唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
⏱三. wait 和 sleep 的对比(面试题)
其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间
他们俩都会让线程进入阻塞,阻塞的原因和目的不同,进入的状态也不同,被唤醒的条件也不一样。
实际开发中会很少使用 sleep ,目的是为了 “放权” ,暂时让出当前 CPU 的使用权。之所以 sleep 使用的少,等待的时间太固定了,要是有突发情况想提前唤醒并不是那么容易,wait 就用的比较多了,wait 可以进行死等,也能设置最长等待时间(涵盖了 sleep 的功能)
1. wait 需要搭配 synchronized 使用. sleep 不需要.
2. wait 是 Object 的方法 sleep 是 Thread 的静态方法.
来源地址:https://blog.csdn.net/m0_67660672/article/details/129505822