我的编程空间,编程开发者的网络收藏夹
学习永远不晚

为什么不用Wait和Notify

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

为什么不用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();     } }

执行结果如下:

为什么不用Wait和Notify

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

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();     } }

程序执行结果如下:

为什么不用Wait和Notify

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

线程“假死”问题分析

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

为什么不用Wait和Notify

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

为什么不用Wait和Notify

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

为什么不用Wait和Notify

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  可以创建多个等待集,以本文的生产者和消费者模型为例,我们可以使用两个等待集,一个用做消费者的等待和唤醒,另一个用来唤醒生产者,这样就不会出现生产者唤醒生产者的情况了(生产者只能唤醒消费者,消费者只能唤醒生产者)这样整个流程就不会“假死”了,它的执行流程如下图所示:

为什么不用Wait和Notify

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

基于 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();     } }

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

为什么不用Wait和Notify

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

2.性能问题

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

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

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

 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.notifyAll();         } 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.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(); }

执行的结果如下图所示:

为什么不用Wait和Notify

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

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

为什么不用Wait和Notify

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

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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

为什么不用Wait和Notify

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎么在Java中正确使用wait, notify和notifyAll

这篇文章将为大家详细讲解有关怎么在Java中正确使用wait, notify和notifyAll,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。wait, notify 和 notifyAll,这些在多线程
2023-06-17

java多线程wait()和notify()如何使用

小编给大家分享一下java多线程wait()和notify()如何使用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!注:wait()和notify()应当用在synchronized内package com.test;im
2023-06-02

Java中怎么正确使用wait-notify方法

本篇内容介绍了“Java中怎么正确使用wait-notify方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1. sleep(long n
2023-06-29

如何使用wait() 和notify() 机制来完成睡眠和踢

今天就跟大家聊聊有关如何使用wait() 和notify() 机制来完成睡眠和踢,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。使用 wait() 和 notify() 机制来完成“睡
2023-06-03

JUC中wait与notify方法的实现原理是什么

今天小编给大家分享一下JUC中wait与notify方法的实现原理是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1.O
2023-07-05

Java中怎么使用wait和notify实现线程间的通信

这篇“Java中怎么使用wait和notify实现线程间的通信”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Java中怎么使
2023-06-30

Java线程通信中wait-notify通信的方式是什么

这篇文章主要介绍“Java线程通信中wait-notify通信的方式是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Java线程通信中wait-notify通信的方式是什么”文章能帮助大家解决问
2023-06-29

Java怎么使用wait或notify实现线程间通信

这篇文章主要介绍“Java怎么使用wait或notify实现线程间通信”,在日常操作中,相信很多人在Java怎么使用wait或notify实现线程间通信问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java怎
2023-07-04

sleep () 和 wait () 有什么区别?

sleep () 方法 sleep () 方法是Thread类的静态方法,可以通过Thread.sleep()来调用。 它是线程的一个静态方法,用于让当前线程休眠一段指定的时间。 sleep () 不会释放线程持有的锁,因此其他线程无
2023-08-30

java中sleep()和wait()有什么区别

这篇文章主要介绍java中sleep()和wait()有什么区别,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!Java是什么Java是一门面向对象编程语言,可以编写桌面应用程序、Web应用程序、分布式系统和嵌入式系统
2023-06-14

sleep()与wait()在Java中有什么不同

sleep()与wait()在Java中有什么不同?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。详解Java中的sleep()和wait()的区别对于sleep()方法,我们首
2023-05-31

java wait和sleep的区别是什么

所以sleep()和wait()方法的最大区别是: ·sleep()睡眠时,保持对象锁,仍然占有该锁; ·而wait()睡眠时,释放对象锁。 ·但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程
java wait和sleep的区别是什么
2021-07-13

python中sleep和wait有什么区别

这期内容当中小编将会给大家带来有关python中sleep和wait有什么区别,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。python可以做什么Python是一种编程语言,内置了许多有效的工具,Pyth
2023-06-14

java中wait和sleep的区别是什么?

sleep:sleep 方法属于 Thread 类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了 sleep方法之后,并不会释放他所持有的所有
java中wait和sleep的区别是什么?
2021-04-11

java中wait和sleep的区别是什么

在Java中,`wait()`和`sleep()`是两个不同的方法,用于不同的场景。1. `wait()`: `wait()`是Object类的方法,是用于线程间的同步和通信。当一个线程执行了一个对象的`wait()`方法后,它会释放该对象
2023-08-08

编程热搜

目录