千家信息网

为什么不用Wait和Notify

发表于:2026-11-04 作者:千家信息网编辑
千家信息网最后更新 2026年11月04日,这篇文章主要介绍"为什么不用Wait和Notify",在日常操作中,相信很多人在为什么不用Wait和Notify问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"为什么不用
千家信息网最后更新 2026年11月04日为什么不用Wait和Notify

这篇文章主要介绍"为什么不用Wait和Notify",在日常操作中,相信很多人在为什么不用Wait和Notify问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"为什么不用Wait和Notify"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

1.notify 线程"假死"

所谓的线程"假死"是指,在使用 notify 唤醒多个等待的线程时,却意外的唤醒了一个没有"准备好"的线程,从而导致整个程序进入了阻塞的状态不能继续执行。

以多线程编程中的经典案例生产者和消费者模型为例,我们先来演示一下线程"假死"的问题。

1.1 正常版本

在演示线程"假死"的问题之前,我们先使用 wait 和 notify 来实现一个简单的生产者和消费者模型,为了让代码更直观,我这里写一个超级简单的实现版本。我们先来创建一个工厂类,工厂类里面包含两个方法,一个是循环生产数据的(存入)方法,另一个是循环消费数据的(取出)方法,实现代码如下。

/**  * 工厂类,消费者和生产者通过调用工厂类实现生产/消费  */ class Factory {     private int[] items = new int[1]; // 数据存储容器(为了演示方便,设置容量最多存储 1 个元素)     private int size = 0;             // 实际存储大小      /**      * 生产方法      */     public synchronized void put() throws InterruptedException {         // 循环生产数据         do {             while (size == items.length) { // 注意不能是 if 判断                 // 存储的容量已经满了,阻塞等待消费者消费之后唤醒                 System.out.println(Thread.currentThread().getName() + " 进入阻塞");                 this.wait();                 System.out.println(Thread.currentThread().getName() + " 被唤醒");             }             System.out.println(Thread.currentThread().getName() + " 开始工作");             items[0] = 1; // 为了方便演示,设置固定值             size++;             System.out.println(Thread.currentThread().getName() + " 完成工作");             // 当生产队列有数据之后通知唤醒消费者             this.notify();          } while (true);     }      /**      * 消费方法      */     public synchronized void take() throws InterruptedException {         // 循环消费数据         do {             while (size == 0) {                 // 生产者没有数据,阻塞等待                 System.out.println(Thread.currentThread().getName() + " 进入阻塞(消费者)");                 this.wait();                 System.out.println(Thread.currentThread().getName() + " 被唤醒(消费者)");             }             System.out.println("消费者工作~");             size--;             // 唤醒生产者可以添加生产了             this.notify();         } while (true);     } }

接下来我们来创建两个线程,一个是生产者调用 put 方法,另一个是消费者调用 take 方法,实现代码如下:

public class NotifyDemo {     public static void main(String[] args) {         // 创建工厂类         Factory factory = new Factory();          // 生产者         Thread producer = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生产者");         producer.start();          // 消费者         Thread consumer = new Thread(() -> {             try {                 factory.take();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "消费者");         consumer.start();     } }

执行结果如下:


从上述结果可以看出,生产者和消费者在循环交替的执行任务,场面非常和谐,是我们想要的正确结果。

1.2 线程"假死"版本

当只有一个生产者和一个消费者时,wait 和 notify 方法不会有任何问题,然而**将生产者增加到两个时就会出现线程"假死"的问题了,**程序的实现代码如下:

public class NotifyDemo {     public static void main(String[] args) {   // 创建工厂方法(工厂类的代码不变,这里不再复述)         Factory factory = new Factory();          // 生产者         Thread producer = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生产者");         producer.start();          // 生产者 2         Thread producer2 = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生产者2");         producer2.start();                  // 消费者         Thread consumer = new Thread(() -> {             try {                 factory.take();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "消费者");         consumer.start();     } }

程序执行结果如下:


从以上结果可以看出,当我们将生产者的数量增加到 2 个时,就会造成线程"假死"阻塞执行的问题,当生产者 2 被唤醒又被阻塞之后,整个程序就不能继续执行了。

线程"假死"问题分析

我们先把以上程序的执行步骤标注一下,得到如下结果:


从上图可以看出:当执行到第 ④ 步时,此时生产者为工作状态,而生产者 2 和消费者为等待状态,此时正确的做法应该是唤醒消费着进行消费,然后消费者消费完之后再唤醒生产者继续工作;但此时生产者却错误的唤醒了生产者 2,而生产者 2 因为队列已经满了,所以自身并不具备继续执行的能力,因此就导致了整个程序的阻塞,流程图如下所示:

正确执行流程应该是这样的:


1.3 使用 Condition

为了解决线程的"假死"问题,我们可以使用 Condition 来尝试实现一下,Condition 是 JUC(java.util.concurrent)包下的类,需要使用 Lock 锁来创建,Condition 提供了 3 个重要的方法:

  • await:对应 wait 方法;

  • signal:对应 notify 方法;

  • signalAll: notifyAll 方法。

Condition 的使用和 wait/notify 类似,也是先获得锁然后在锁中进行等待和唤醒操作,Condition 的基础用法如下:

// 创建 Condition 对象 Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); // 加锁 lock.lock(); try {     // 业务方法....          // 1.进入等待状态     condition.await();      // 2.唤醒操作     condition.signal(); } catch (InterruptedException e) {     e.printStackTrace(); } finally {     lock.unlock(); }

小知识:Lock的正确使用姿势

切记 Lock 的 lock.lock() 方法不能放入 try 代码中,如果 lock 方法在 try 代码块之内,可能由于其它方法抛出异常,导致在 finally 代码块中, unlock 对未加锁的对象解锁,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),抛出 IllegalMonitorStateException 异常。

回归主题

回到本文的主题,我们如果使用 Condition 来实现线程的通讯就可以避免程序的"假死"情况,因为 Condition 可以创建多个等待集,以本文的生产者和消费者模型为例,我们可以使用两个等待集,一个用做消费者的等待和唤醒,另一个用来唤醒生产者,这样就不会出现生产者唤醒生产者的情况了(生产者只能唤醒消费者,消费者只能唤醒生产者)这样整个流程就不会"假死"了,它的执行流程如下图所示:


了解了它的基本流程之后,咱们来看具体的实现代码。

基于 Condition 的工厂实现代码如下:

class FactoryByCondition {     private int[] items = new int[1]; // 数据存储容器(为了演示方便,设置容量最多存储 1 个元素)     private int size = 0;             // 实际存储大小     // 创建 Condition 对象     private Lock lock = new ReentrantLock();     // 生产者的 Condition 对象     private Condition producerCondition = lock.newCondition();     // 消费者的 Condition 对象     private Condition consumerCondition = lock.newCondition();      /**      * 生产方法      */     public void put() throws InterruptedException {         // 循环生产数据         do {             lock.lock();             while (size == items.length) { // 注意不能是 if 判断                 // 生产者进入等待                 System.out.println(Thread.currentThread().getName() + " 进入阻塞");                 producerCondition.await();                 System.out.println(Thread.currentThread().getName() + " 被唤醒");             }             System.out.println(Thread.currentThread().getName() + " 开始工作");             items[0] = 1; // 为了方便演示,设置固定值             size++;             System.out.println(Thread.currentThread().getName() + " 完成工作");             // 唤醒消费者             consumerCondition.signal();             try {             } finally {                 lock.unlock();             }         } while (true);     }      /**      * 消费方法      */     public void take() throws InterruptedException {         // 循环消费数据         do {             lock.lock();             while (size == 0) {                 // 消费者阻塞等待                 consumerCondition.await();             }             System.out.println("消费者工作~");             size--;             // 唤醒生产者             producerCondition.signal();             try {             } finally {                 lock.unlock();             }         } while (true);     } }

两个生产者和一个消费者的实现代码如下:

public class NotifyDemo {     public static void main(String[] args) {         FactoryByCondition factory = new FactoryByCondition();          // 生产者         Thread producer = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生产者");         producer.start();          // 生产者 2         Thread producer2 = new Thread(() -> {             try {                 factory.put();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "生产者2");         producer2.start();          // 消费者         Thread consumer = new Thread(() -> {             try {                 factory.take();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }, "消费者");         consumer.start();     } }

程序的执行结果如下图所示:


从上述结果可以看出,当使用 Condition 时,生产者、消费者、生产者 2 会一直交替循环执行,执行结果符合我们的预期。

2.性能问题

在上面我们演示 notify 会造成线程的"假死"问题的时候,一定有朋友会想到,如果把 notify 换成 notifyAll 线程就不会"假死"了。

这样做法确实可以解决线程"假死"的问题,但同时会到来新的性能问题,空说无凭,直接上代码展示。

以下是使用 wait 和 notifyAll 改进后的代码:

/**  * 工厂类,消费者和生产者通过调用工厂类实现生产/消费功能.  */ class Factory {     private int[] items = new int[1];   // 数据存储容器(为了演示方便,设置容量最多存储 1 个元素)     private int size = 0;               // 实际存储大小      /**      * 生产方法      * @throws InterruptedException      */     public synchronized void put() throws InterruptedException {         // 循环生产数据         do {             while (size == items.length) { // 注意不能是 if 判断                 // 存储的容量已经满了,阻塞等待消费者消费之后唤醒                 System.out.println(Thread.currentThread().getName() + " 进入阻塞");                 this.wait();                 System.out.println(Thread.currentThread().getName() + " 被唤醒");             }             System.out.println(Thread.currentThread().getName() + " 开始工作");             items[0] = 1; // 为了方便演示,设置固定值             size++;             System.out.println(Thread.currentThread().getName() + " 完成工作");             // 唤醒所有线程             this.notifyAll();         } while (true);     }      /**      * 消费方法      * @throws InterruptedException      */     public synchronized void take() throws InterruptedException {         // 循环消费数据         do {             while (size == 0) {                 // 生产者没有数据,阻塞等待                 System.out.println(Thread.currentThread().getName() + " 进入阻塞(消费者)");                 this.wait();                 System.out.println(Thread.currentThread().getName() + " 被唤醒(消费者)");             }             System.out.println("消费者工作~");             size--;             // 唤醒所有线程             this.notifyAll();         } while (true);     } }

依旧是两个生产者加一个消费者,实现代码如下:

public static void main(String[] args) {     Factory factory = new Factory();     // 生产者     Thread producer = new Thread(() -> {         try {             factory.put();         } catch (InterruptedException e) {             e.printStackTrace();         }     }, "生产者");     producer.start();      // 生产者 2     Thread producer2 = new Thread(() -> {         try {             factory.put();         } catch (InterruptedException e) {             e.printStackTrace();         }     }, "生产者2");     producer2.start();      // 消费者     Thread consumer = new Thread(() -> {         try {             factory.take();         } catch (InterruptedException e) {             e.printStackTrace();         }     }, "消费者");     consumer.start(); }

执行的结果如下图所示:

通过以上结果可以看出:当我们调用 notifyAll 时确实不会造成线程"假死"了,但会造成所有的生产者都被唤醒了,但因为待执行的任务只有一个,因此被唤醒的所有生产者中,只有一个会执行正确的工作,而另一个则是啥也不干,然后又进入等待状态,这种行为对于整个程序来说,无疑是多此一举,只会增加线程调度的开销,从而导致整个程序的性能下降。

反观 Condition 的 await 和 signal 方法,即使有多个生产者,程序也只会唤醒一个有效的生产者进行工作,如下图所示:


生产者和生产者 2 依次会被交替的唤醒进行工作,所以这样执行时并没有任何多余的开销,从而相比于 notifyAll 而言整个程序的性能会提升不少。

到此,关于"为什么不用Wait和Notify"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

生产 生产者 消费 消费者 方法 线程 代码 数据 工作 阻塞 程序 问题 结果 存储 工厂 循环 演示 两个 容量 对象 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技有限公司经营范围 厦门中拓互联网络科技有限公司 新华互联网科技学院 中资互联网科技 易互联网科技有限公司 云电互联网科技 无锡溥仪互联网科技有限公司 银川新华互联网科技学校地址 广州优美互联网科技 世界互联网科技公司市值排名 恒生互联网科技指数跳水 米聚互联网科技南京有限公司排面 亳州好赞互联网科技有限公司 图游互联网科技 工业互联网科技创业 南京一网一红互联网科技有限公司 上海乐五互联网科技有限公司 悟空问答互联网科技 华人运通互联网科技公司 安徽九吞吴互联网科技有限公司 丰台科技园 互联网 济南和联互联网科技有限公司 互联网科技公司实习生 深圳市罗太互联网科技有限公司 互联网科技的全部知识 北京市互联网新科技 浙南科技城互联网大厦招聘 上海互联网科技公司ppt 南京一网一红互联网科技有限公司 科技互联网企业在行动
0