欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 国际 > 【JavaEE初阶 — 多线程】wait() notify()

【JavaEE初阶 — 多线程】wait() notify()

2024/11/16 10:45:49 来源:https://blog.csdn.net/2402_84916296/article/details/143666332  浏览:    关键词:【JavaEE初阶 — 多线程】wait() notify()

    c96f743646e841f8bb30b2d242197f2f.gif

ddb5ae16fc92401ea95b48766cb03d96.jpeg692a78aa0ec843629a817408c97a8b84.gif

    1. 协调多个线程之间的执行先后顺序的方法介绍    


由于线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知;但是实际开发中,有时候我们希望合理地协调多个线程之间的执行先后顺序。

    拓展: wait() 和 sleep() 的区别     


wait() 和 sleep()都是用于暂停线程的操作,但它们有明显的区别(先说面试官最关心的):


    (1)使用要求不同    


  • wait() 必须在同步块或同步方法内调用(嵌套一层 synchronized ),否则会抛出IllegalMonitorStateException。
  • 这是因为 wait() 依赖于对象锁来管理线程的等待和唤醒机制。
  • 调用后,当前线程会释放它持有的对象锁,并进入等待状态。

  • sleep()方法可以在任何上下文中调用,不需要获取对象锁
  • 调用后,线程会进入休眠状态,但不会释放它持有的任何锁
  • 所以如果 wait() 和 sleep() 都嵌套一层锁,分别被唤醒时,wait() 会释放锁,而 sleep() 不会释放锁;

    (2)方法所属类不同    


  • wait()   :属于 Object 类的非静态方法。
  • sleep() :属于 Thread 类的静态方法。

    (3)恢复方式不同    


  • wait() 需要被其他线程通过 notify() 或 notifyAll() 显式唤醒;
  • 或者被 wait(long timeout) 的超时参数唤醒。

  • sleep() 在指定时间后自动恢复运行,或通过 interrupt() 提前唤醒,抛出 InterruptedException 异常

    (4)用途不同    


  • wait() 通常用于线程间通信,配合 notify() 或 notifyAll() 来实现线程的协调工作。

  • sleep() 用于让线程暂停执行一段时间,通常用于控制线程的执行频率或模拟延时

     (5)常见错误     


     误用sleep() :    

有时开发者会错误地使用 sleep() 进行线程间通信,但是 sleep() 不释放锁,可能会导致其他线程无法进入同步块,造成线程饥饿或死锁。


     忽略中断:    

sleep() 可能抛出 InterruptedException , 如果不正确处理中断信号,可能会导致线程提前退出或错误行为。


   2. wait()     


   2.1  线程饿死    

如上图,鸟妈妈(CPU)抓虫(调度资源)喂小鸟(线程),就是一个典型的 “线程饿死” 情景:

  • 对于线程饿死,并不是鸟妈妈把捉到的虫全都喂一只鸟宝宝,其他鸟宝宝一点都吃不到,而是鸟妈妈把捉到的虫子,绝大多数都喂给了一个鸟宝宝,剩下的鸟宝宝只能吃到一点点;
  • 使用 wait(),notify() 就是为了优化 “鸟妈妈把大多数的虫子,都分给一只鸟宝宝” 这一行为。

    2.2  调用 wait()     


  • wait() 和 notify 都是Object 的方法;Java 中的任意对象都提供了 wait() 和 notify();
  • wait() 能使当前执行代码的线程进行等待(把线程放到等待队列中);
  • wait() 一被调用,就会释放当前的锁;
  • 满足一定条件时被唤醒,重新尝试获取这个锁;
  • 注意:在判断是否满足唤醒条件时,我们可以把 if(判断条件)  改成 while(判断条件) ,这样可以避免被 interrupt() 类似的方法非法打断。在该文章模拟阻塞队列的 put() 和 take() 有详细解释 


  • wait 要搭配 synchronized 来使用;脱离synchronized,使用 wait 会直接抛出上述异常;
  • 上述异常被抛出的本质,是针对未加锁的锁对象进行释放锁操作


    2.3 唤醒 wait()    

  • 其他线程调用该对象的 notify() ;
  • wait() 等待时间超时(wait() 提供一个带有 timeout 参数的版本,来指定等待时间);
  • 其他线程调用该等待线程的 interrupted(),导致 wait() 抛出  InterruptedException 异常;

  • 在synchronized的代码块中,等到wait() 结束,wait后面到 }的部分,还有一些其他的逻辑;
  • 这些逻辑还是期望在锁的范围内进行调度,所以 wait 后面到}的部分也要嵌套在锁内,wait() 被唤醒后,会重新对这些逻辑上锁,以保证线程安全。

   3. notify()   


    3.1 wait() 和 notify() 的需要同一个对象调用    


  • 通过相同的对象调用 wait() 和 notify() ,是两个线程沟通的桥梁;
  • wait() 和 notify() 针对同一个对象才能生效;如果是不同对象,则没有任何相互的影响和作用~
  • 为了验证这一点,外面写出如下代码:

    代码逻辑:   

  • 在 t1 线程执行到第一个打印日志之后,执行 wait() ,此时就需要通过 t2 线程来唤醒 t1;
  • 我们先用 Scaner 来阻塞 t2,这样操作就可以手动控制 t2 对 t1 的唤醒;
  • 在输入内容后,t2执行 notify(),如果调用 wait() 和 notify() 的两个对象相同,则 t1 会成功被唤醒。

上图是不同对象调用 wait() 和 notify() 的情况,我们再来看看相同对象调用的结果:


    3.2  notify() 要同步方法或同步块中调用    


和 wait() 一样,也是要在同步方法或同步块(嵌套一层 synchronized)中调用;


    3.3 notify() 随机唤醒多个 wait() 中的其中一个    


  • notify()通知正在 wait(), 等待同一个对象锁的其它线程,对其它线程中的一个线程,发出通知notify,并使这个线程重新获取该对象的对象锁 
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈wait状态的线程。(并没有"先来后到")

  • 我们来看程序运行结果,在输入任意内容后,打印的结束日志是不一样的

  • 这就证明了线程调度器随机挑选出一个呈 wait 状态的线程
  • 有两个 wait(),一个 notify(),一定有一个线程没有被唤醒,导致整个进行无法结束;
  • 既然有两个 wait(),我们就设置两个 notify() 即可解决该问题;


  • 在 notify()方法后,当前线程不会马上释放该对象锁;
  • 要等到执行 notify() 所在同步代码块退出之后,才会释放对象锁 ;

   4. notifyAll()   


  •  对于刚刚上面写的代码,只有一个 notify(),就只能唤醒一个 wait():

  • 因此,我们可以考虑用 notifyAll(),唤醒所有相同对象调用 wait() 的线程:

  • 虽然同时唤醒了 t1 和 t2,但是由于 wait() 被唤醒之后,要重新加锁;因此其中某个线程,先加上锁,开始执行,而另一个线程因为加锁失败,再次阻塞等待;
  • 等到先加锁的线程解锁了,后加锁的线程才能加上锁,而继续执行~

    总结    

  • 因为这个原因,notifyAll() 在实际开发中,虽然可以唤醒所有 wait() ,但是用的并不多。
  • 因为不是一口气全部唤醒 wait(),而是每次唤醒其中一个线程,通过多次唤醒,把所有 wait()状态的线程唤醒;
  • notifyAll() 在唤醒其中一个 wait() 状态的线程时,其他线程依旧因为 wait() 尝试重新获取锁对象,而陷入阻塞等待;
  • 比起唤醒所有,我们更希望通过一个一个的 notify() 精确唤醒每一个线程。

     5. 应用 wait() 和 notify() 解决编程题    


    5.1 题目    


     5.2 程序运行结果     


    5.3 完整代码     

package Thread;public class Demo29 {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");synchronized (locker2){locker2.notify();}}} catch (InterruptedException e) {throw new RuntimeException(e);}});Thread t2 = new Thread(() -> {try {for (int i = 0; i < 10; i++) {synchronized (locker2) {locker2.wait();}System.out.print("B");synchronized (locker3){locker3.notify();}}} catch (InterruptedException e) {throw new RuntimeException(e);}});Thread t3 = new Thread(() -> {try {for (int i = 0; i < 10; i++) {synchronized (locker3) {locker3.wait();}System.out.println("C");synchronized (locker1){locker1.notify();}}} catch (InterruptedException e) {throw new RuntimeException(e);}});t1.start();t2.start();t3.start();//轮子已经造好了,现在需要推一把,让轮子转起来//需要确保上述主线程都执行到 wait(),再推轮子Thread.sleep(1000);synchronized (locker1){locker1.notify();}}
}

    c96f743646e841f8bb30b2d242197f2f.gif

692a78aa0ec843629a817408c97a8b84.gif

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com