浅谈Java线程间通信之wait/notify

发布时间 - 2026-01-11 01:56:12    点击率:

Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式。先来我们来看下相关定义:

wait() :调用该方法的线程进入WATTING状态,只有等待另外线程的通知或中断才会返回,调用wait()方法后,会释放对象的锁。

wait(long):超时等待最多long毫秒,如果没有通知就超时返回。

notify() :通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁。

notifyAll():通知所有等待在该对象上的线程。

一个小例子

我们来模拟个简单的例子来说明,我们楼下有个小小的饺子馆,生意火爆,店里有一个厨师,一个服务员,为避免厨师每做好一份,服务员端出去一份,效率太低且浪费体力。现假设厨师每做好10份,服务员就用一个大木盘子端给客户,每天卖够100份就打烊收工,厨师服务员各自回家休息。

思考一下,要实现该功能,如果不使用等待/通知机制,那么最直接的方式可能就是,服务员隔一段时间去厨房看看,满10份就用盘子端出去。

这种方式有两个很大的弊病:

1.如果服务员去厨房看的太勤快,服务员太累了,这样还不如每做一碗就端一碗给客人,大木盘子的作用就体现不出来了。具体表现在实现代码层面就是:需要不断的循环,浪费处理器资源。

2.如果服务员隔很久才去厨房看一下,就无法确保及时性了,可能厨师早都做够10份了,服务员却没观察到。

针对上面这个例子,使用等待/通知机制就合理的多了,厨师每做够10份,就喊一声“饺子好了,可以端走啦”。服务员收到通知,就去厨房将饺子端给客人;厨师还没做够,即还没收到厨师的通知,就可以稍微休息下,但也得竖起耳朵等候厨师的通知。 

package ConcurrentTest;

import thread.BlockQueue;

/**
 * Created by chengxiao on 2017/6/17.
 */
public class JiaoziDemo {
  //创建个共享对象做监视器用
  private static Object obj = new Object();
  //大木盘子,一盘最多可盛10份饺子,厨师做满10份,服务员就可以端出去了。
  private static Integer platter = 0;
  //卖出的饺子总量,卖够100份就打烊收工
  private static Integer count = 0;

  /**
   * 厨师
   */
  static class Cook implements Runnable{
    @Override
    public void run() {
      while(count<100){
        synchronized (obj){
          while (platter<10){
            platter++;
          }
          //通知服务员饺子好了,可以端走了
          obj.notify();
          System.out.println(Thread.currentThread().getName()+"--饺子好啦,厨师休息会儿");
        }
        try {
          //线程睡一会,帮助服务员线程抢到对象锁
          Thread.sleep(100);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      System.out.println(Thread.currentThread().getName()+"--打烊收工,厨师回家");
    }
  }

  /**
   * 服务员
   */
  static class Waiter implements Runnable{
    @Override
    public void run() {
      while(count<100){
        synchronized (obj){
          //厨师做够10份了,就可以端出去了
          while(platter < 10){
            try {
              System.out.println(Thread.currentThread().getName()+"--饺子还没好,等待厨师通知...");
              obj.wait();
              BlockQueue
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
          }
          //饺子端给客人了,盘子清空
          platter-=10;
          //又卖出去10份。
          count+=10;
          System.out.println(Thread.currentThread().getName()+"--服务员把饺子端给客人了");
        }
      }
      System.out.println(Thread.currentThread().getName()+"--打烊收工,服务员回家");

    }
  }
  public static void main(String []args){
    Thread cookThread = new Thread(new Cook(),"cookThread");
    Thread waiterThread = new Thread(new Waiter(),"waiterThread");
    cookThread.start();
    waiterThread.start();
  }
}

运行结果

cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--饺子还没好,等待厨师通知...
cookThread--饺子好啦,厨师休息会儿
waiterThread--服务员把饺子端给客人了
waiterThread--打烊收工,服务员回家
cookThread--打烊收工,厨师回家

运行机制

借用《并发编程的艺术》中的一张图来了解下wait/notify的运行机制

可能有人会对所谓监视器(monitor),对象锁(lock)不甚了解,在此简单解释下:

jvm为每一个对象和类都关联一个锁,锁住了一个对象,就是获得了对象相关联的监视器。

只有获取到对象锁,才能拿到监视器,如果获取锁失败了,那么线程就会进入阻塞队列中;如果成功拿到对象锁,也可以使用wait()方法,在监视器上等待,此时会释放锁,并进入等地队列中。

关于锁和监视器的区别,有篇文章写得很详细透彻,在此引用一下,有兴趣的童鞋可以了解一下详谈锁和监视器之间的区别_Java并发

根据上面的图我们来理一下具体的过程

1.首先,waitThread获取对象锁,然后调用wait()方法,此时,wait线程会放弃对象锁,同时进入对象的等待队列WaitQueue中;

2.notifyThread线程抢占到对象锁,执行一些操作后,调用notify()方法,此时会将等待线程waitThread从等待队列WaitQueue中移到同步队列SynchronizedQueue中,waitThread由waitting状态变为blocked状态。需要注意的时,notifyThread此时并不会立即释放锁,它继续运行,把自己剩余的事儿干完之后才会释放锁;

3.waitThread再次获取到对象锁,从wait()方法返回继续执行后续的操作;

4.一个基于等待/通知机制的线程间通信的过程结束。

至于notifyAll则是在第二步中将等待队列中的所有线程移到同步队列中去。

避免踩坑

在使用wait/notify/notifyAll时有一些特别留意的,在此再总结一下:

1.一定在synchronized中使用wait()/notify()/notifyAll(),也就是说一定要先获取锁,这个前面我们讲过,因为只有加锁后,才能获得监视器。否则jvm也会抛出IllegalMonitorStateException异常。

2.使用wait()时,判断线程是否进入wait状态的条件一定要使用while而不要使用if,因为等待线程可能会被错误地唤醒,所以应该使用while循环在等待前等待后都检查唤醒条件是否被满足,保证安全性。

3.notify()或notifyAll()方法调用后,线程不会立即释放锁。调用只会将wait中的线程从等待队列移到同步队列,也就是线程状态从waitting变为blocked;

4.从wait()方法返回的前提是线程重新获得了调用对象的锁。

后记

关于wait/notify的相关内容就介绍到此,在实际使用中,要特别留意上文中提到的几点,不过一般情况下,我们直接使用wait/notify/notifyAll去完成线程间通信,生产者/消费者模型的机会不多,因为Java并发包中已经提供了很多优秀精妙的工具,像各种BlockingQueue等等,后面有机会也会详细介绍的。

以上这篇浅谈Java线程间通信之wait/notify就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。


# java线程wait  # notify  # Java使用wait/notify实现线程间通信上篇  # Java通过wait()和notifyAll()方法实现线程间通信  # Java使用wait/notify实现线程间通信下篇  # 还没  # 好啦  # 会儿  # 大木  # 在此  # 端出  # 移到  # 来了  # 好了  # 也会  # 去了  # 就可以  # 最多  # 才会  # 给大家  # 就用  # 会将  # 来实现  # 运行机制  # 都是 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 微信h5制作网站有哪些,免费微信H5页面制作工具?  Laravel模型关联查询教程_Laravel Eloquent一对多关联写法  php8.4header发送头信息失败怎么办_php8.4header函数问题解决【解答】  BootStrap整体框架之基础布局组件  Laravel如何与Docker(Sail)协同开发?(环境搭建教程)  详解jQuery停止动画——stop()方法的使用  Laravel如何使用Scope本地作用域_Laravel模型常用查询逻辑封装技巧【手册】  网页制作模板网站推荐,网页设计海报之类的素材哪里好?  如何用低价快速搭建高质量网站?  Laravel怎么实现软删除SoftDeletes_Laravel模型回收站功能与数据恢复【步骤】  为什么要用作用域操作符_php中访问类常量与静态属性的优势【解答】  Laravel如何实现多对多模型关联?(Eloquent教程)  Windows10电脑怎么查看硬盘通电时间_Win10使用工具检测磁盘健康  Linux网络带宽限制_tc配置实践解析【教程】  Laravel如何使用Blade组件和插槽?(Component代码示例)  Laravel Sail是什么_基于Docker的Laravel本地开发环境Sail入门  Laravel如何实现一对一模型关联?(Eloquent示例)  Laravel怎么做缓存_Laravel Cache系统提升应用速度的策略与技巧  千问怎样用提示词获取健康建议_千问健康类提示词注意事项【指南】  如何快速查询域名建站关键信息?  如何在 Pandas 中基于一列条件计算另一列的分组均值  jimdo怎样用html5做选项卡_jimdo选项卡html5实现与切换效果【指南】  Android滚轮选择时间控件使用详解  如何实现建站之星域名转发设置?  千库网官网入口推荐 千库网设计创意平台入口  如何用虚拟主机快速搭建网站?详细步骤解析  详解jQuery中的事件  Android使用GridView实现日历的简单功能  如何用免费手机建站系统零基础打造专业网站?  悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音  Android利用动画实现背景逐渐变暗  如何用搬瓦工VPS快速搭建个人网站?  Laravel如何保护应用免受CSRF攻击?(原理和示例)  如何正确选择百度移动适配建站域名?  如何用AI帮你把自己的生活经历写成一个有趣的故事?  Laravel如何实现用户注册和登录?(Auth脚手架指南)  北京企业网站设计制作公司,北京铁路集团官方网站?  Javascript中的事件循环是如何工作的_如何利用Javascript事件循环优化异步代码?  微信小程序 闭包写法详细介绍  网易LOFTER官网链接 老福特网页版登录地址  Laravel如何与Pusher实现实时通信?(WebSocket示例)  专业商城网站制作公司有哪些,pi商城官网是哪个?  PHP怎么接收前端传的文件路径_处理文件路径参数接收方法【汇总】  如何快速搭建FTP站点实现文件共享?  Laravel如何创建自定义中间件?(Middleware代码示例)  JavaScript如何实现类型判断_typeof和instanceof有什么区别  JS弹性运动实现方法分析  Windows11怎样设置电源计划_Windows11电源计划调整攻略【指南】  大学网站设计制作软件有哪些,如何将网站制作成自己app?  如何在橙子建站上传落地页?操作指南详解