java通过 notify和 wait 实现线程间的通信

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益:

  1. 了解大厂经验
  2. 拥有和大厂相匹配的技术等

希望看什么,评论或者私信告诉我!

文章目录

  • 一、前言
  • 二、notify 和 wait
    • 2.1 wait
      • 2.1.1 wait 基本介绍
      • 2.1.2 wait 注意点
      • 2.1.3 wait 使用场景
      • 2.1.4 wait 的执行原理
      • 2.1.5 wait 使用
    • 2.2 notify
      • 2.2.1 notify 基本介绍
      • 2.2.2 notify 注意点
      • 2.2.3 nofityall
      • 2.2.3 notify 使用场景
      • 2.2.4 notify 的执行原理
      • 2.2.5 notify和notifyAll 注意事项和要点:
  • 三、wait/nofity 经典使用方式
  • 四、总结


一、前言

在软件开发中,线程是实现并发执行的重要手段,然而,线程之间的协作与通信却是开发者必须重点考虑的挑战之一。Java作为一种广泛应用于多线程编程的语言,提供了一套强大而灵活的机制,让不同线程之间能够优雅地交替执行、传递信息,以实现协调合作。

前面我们已经完成了多线程一些基础知识准备以及 volatile 和 synchronized,本文将深入探讨Java中通过notifywait实现线程间通信的机制。

二、notify 和 wait

2.1 wait

2.1.1 wait 基本介绍

通过源码我们可以知道 wait 是 object 对象方法,用于实现线程间的等待和通知机制。当一个线程调用wait()方法时,它会释放当前所持有的对象锁,并进入等待状态,直到被其他线程调用相同对象上的notify()notifyAll()方法唤醒。

public final void wait() throws InterruptedException {wait(0);
}

2.1.2 wait 注意点

  1. 要想使用wait方法,当前线程必须拥有对应 object 的 mointor,即必须要拥有对应对象的锁,否则会报java.lang.IllegalMonitorStateException
    比如:
Object o = new Object();
o.wait(); //java.lang.IllegalMonitorStateException
  1. 当调用wait()方法的线程被另一个线程中断时,wait()方法会抛出InterruptedException异常。
  2. 线程也可能会在没有收到通知、中断或超时的情况下被唤醒,即所谓的虚假唤醒。虽然这种情况在实践中很少发生,但应用程序必须通过测试应该导致线程被唤醒的条件来防范这种情况,如果条件不满足,则继续等待。换句话说,等待应该总是在循环中发生,如下所示:
   synchronized (obj) {while (<condition does not hold>)obj.wait(timeout);... // Perform action appropriate to condition}

2.1.3 wait 使用场景

wait()方法是在Java中用于线程间通信和同步的重要方法之一。它通常与synchronized关键字一起使用,用于在多线程中协调线程之间的操作。下面是wait()方法的一些常见使用场景:

  1. 协调多个线程的操作wait()方法通常与notify()notifyAll()方法一起使用,用于在多线程之间协调操作。一个线程可以调用wait()进入等待状态,等待其他线程调用相同对象的notify()notifyAll()方法来唤醒它。
  2. 等待条件满足:线程可以调用wait()方法等待某个条件的满足。例如,一个线程可能等待某个变量的值发生改变或者等待某个事件发生。
  3. 线程安全的队列实现wait()方法常用于实现线程安全的队列。当队列为空时,消费者线程调用wait()等待生产者线程向队列中添加元素;当队列已满时,生产者线程调用wait()等待消费者线程从队列中取走元素。
  4. 实现生产者-消费者模型wait()方法在生产者-消费者模型中起着重要作用。生产者向共享的缓冲区添加数据时,如果缓冲区已满,生产者线程调用wait()等待消费者取走数据;消费者从缓冲区取数据时,如果缓冲区为空,消费者线程调用wait()等待生产者添加数据。
  5. 线程间通信wait()方法是线程间通信的重要手段之一。线程可以通过等待并唤醒的机制来实现信息的传递和协调。

2.1.4 wait 的执行原理

  • 当线程调用wait()方法时,它会释放当前持有的对象锁,使得其他线程可以访问这个对象并执行同步代码块。
  • 调用wait()方法的线程会进入对象的等待队列,等待其他线程调用相同对象的notify()notifyAll()方法来唤醒它。
  • 当另一个线程调用相同对象notify()方法或notifyAll()方法时,等待队列中的线程会被唤醒,然后竞争对象的锁。
  • 唤醒的线程会尝试重新获取对象锁,然后继续执行。

2.1.5 wait 使用

  1. wait()或者wait(0): 调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,需要注意调用 wait() 方法后,会释放对象的锁
  2. wait(long) 会释放锁,超时等待一段时间,这里的参数时间是毫秒,也就是等待长达毫秒,如果没有通知就超时返回
  3. wait(long,int) 会释放锁,该方法与 wait(long) 类似,多了一个 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999), 所以超时的时间还需要加上 nanos 纳秒。

2.2 notify

首先我们需要知道 nofity 和 notifyall 基本上是等价的,如没有特别标明,nofity 和 nofityall 是一样的

2.2.1 notify 基本介绍

通过源码我们可以知道 notify 是 object 对象方法。

notify()是线程间通信的一种机制,用于唤醒在当前对象上等待的一个线程。当一个线程调用notify()方法时,它会唤醒正在该对象上等待的单个线程(如果有多个线程在等待,系统无法确定哪个线程会被唤醒,因为选择是随机的)。这个被唤醒的线程将从等待状态变为可运行状态,但并不意味着立即获得对象的锁。

public final native void notify();

当一个线程调用notifyAll()时,它会唤醒在当前对象上等待的所有线程,使它们从等待状态转变为可运行状态。这样,所有等待中的线程都有机会争取获取对象锁,在某个线程获得锁后,它们会竞争执行。

2.2.2 notify 注意点

下面是关于notify()方法的一些重要点:

  1. 使用条件变量notify()方法通常与条件变量一起使用,用于线程间的协作。一个线程等待某个条件变为真,另一个线程在某种情况下会改变条件,并调用notify()来通知等待的线程。
  2. notifyAll()方法:与notify()不同的是,notifyAll()方法唤醒在当前对象上等待的所有线程,而不仅仅是一个线程。这样做可以避免遗漏任何等待中的线程,但同一时间获取锁的只会有一个线程。
  3. Object类中的方法notify()方法是Object类的一个方法,因此任何Java对象都可以调用notify()wait()方法来进行线程间的通信。
  4. 必须在同步块中调用:为了调用notify()方法,必须在同步块(synchronized块)中对包含该对象的锁进行操作。
  5. 随机性:当多个线程在同一个对象上等待时,调用notify()会随机选择一个线程唤醒,因此不能确定哪个线程会被唤醒。
  6. 唤醒等待线程:被唤醒的线程会尝试重新获取对象锁,一旦获得锁,它会从wait()方法返回,并继续执行后续代码。

2.2.3 nofityall

以下是关于notifyAll()方法的详细介绍:

  1. 唤醒所有等待线程notifyAll()方法被调用时,会唤醒在当前对象上等待的所有线程,使它们从阻塞状态转变为就绪状态。这样,所有等待中的线程都有机会去竞争获取对象的锁。
  2. 解决等待问题notifyAll()通常用于解决多线程之间的通信和协调问题。当某个条件得到满足时,调用notifyAll()可以通知所有等待该条件的线程。
  3. 谨慎使用:需要谨慎使用notifyAll(),因为唤醒所有线程可能会导致竞争和性能问题。在某些情况下,如果只有一个线程是合适的接收方,那么使用notify()来唤醒特定线程会更有效率。
  4. 必须在同步块中调用:就像notify()方法一样,notifyAll()也必须在同步块(synchronized块)内调用,以确保在对象上同步。通过同步块,确保在调用notifyAll()时,持有对象的锁。
  5. 竞争获取锁:被唤醒的线程会尝试获取对象的锁,一旦获得锁,它们将从wait()方法返回,并开始竞争执行。
  6. 释放锁:调用notifyAll()并不会释放对象锁,它仅唤醒等待的线程,这些线程会在合适的时机争夺锁。

2.2.3 notify 使用场景

notify()的使用场景:

  • 单个唤醒:当只需唤醒等待队列中的一个线程时,适合使用notify()。这种情况下,最多只有一个等待线程被唤醒,选择被唤醒的线程是不确定的。
  • 资源更新:当某个共享资源的状态发生变化时,并且只需要通知一个线程来处理这种变化时,可以使用notify()

notifyAll()的使用场景:

  • 多个唤醒:当需要唤醒所有等待线程来处理某个共享资源的变化时,应该使用notifyAll()。这种情况下,所有等待线程都会被唤醒。
  • 条件变更:当共享资源的状态发生变化对多个线程都有影响时,可以使用notifyAll()来通知所有等待线程,因为多个线程可能对资源的状态变化做出不同的响应。
  • 避免竞争问题:在某些情况下,使用notifyAll()可以避免因为只唤醒一个线程而导致的竞争问题,确保所有等待线程都能及时做出响应。

2.2.4 notify 的执行原理

notify()方法的执行原理:

  1. 当一个线程调用对象的notify()方法时,这个对象的监视器被激活。
  2. 在等待该对象的线程中,会有一个线程从等待队列中被选中,进入到锁定状态,并尝试重新获取对象的锁。
  3. 一旦获取到锁,该线程会从wait()方法返回,并继续执行。
  4. 其他等待该对象的线程仍然处于等待状态,需要重新竞争获取对象的锁来继续执行。

notifyAll()方法的执行原理:

  1. 当一个线程调用对象的notifyAll()方法时,所有等待该对象的线程将被唤醒。
  2. 被唤醒的线程会一起竞争对象的锁,以便能够继续执行。
  3. 每个被唤醒的线程尝试重新获取锁,一旦成功获取到锁,它们会从wait()方法返回,并接着执行后续代码。

2.2.5 notify和notifyAll 注意事项和要点:

  • 使用 notify和notifyAll 时需要先对调用的对象加锁,否则会报错
  • notify()方法和notifyAll()方法必须在同步块中(synchronized块)调用,以确保在调用这些方法时对象的锁处于正确状态。
  • 被唤醒的线程在获取到锁并从wait()方法返回后,可能需要检查等待期间的条件是否发生了变化,从而决定是否继续执行。

三、wait/nofity 经典使用方式

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;public class WaitNotify {static boolean flag = true;static Object  lock = new Object();public static void main(String[] args) {Thread waitThread = new Thread(new Wait(), "waitThread");waitThread.start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(new Notify(), "notifyThread").start();}//waitstatic class Wait implements Runnable {public void run() {synchronized (lock) {while (flag) {try {System.out.println(Thread.currentThread() + " flag is true" + new SimpleDateFormat("HH:mm:ss").format(new Date()));//没有释放资源 wait 等待lock.wait();} catch (InterruptedException e) {}}System.out.println(Thread.currentThread() + " flag is false" + new SimpleDateFormat("HH:mm:ss").format(new Date()));}}}static class Notify implements Runnable {public void run() {synchronized (lock) {while (flag) {System.out.println(Thread.currentThread() + "hold lock  notify" + new SimpleDateFormat("HH:mm:ss").format(new Date()));//资源准备好了,nofitylock.notifyAll();flag = false;try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}}synchronized (lock) {System.out.println(Thread.currentThread() + "hold lock again. notify" + new SimpleDateFormat("HH:mm:ss").format(new Date()));try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}}}
}

这是最经典的等待/通知的范式:但资源没有准备好时,wait 等待,当资源准备好了,notify 通知。这里也留一个思考题:这里的 flag 为什么不需要用 volatile 修饰,答案在这里:多线程一些基础知识准备以及 volatile 和 synchronized

四、总结

文章详细讲解了Java中wait和notify方法的使用方法和注意事项,通过这些方法实现线程间的协调与通信,并列举了常见的使用场景,如协调多个线程操作、实现生产者-消费者模型等。此外,文章还强调了wait和notify方法必须在同步块中调用,以确保线程安全。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/32796.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【专业英语 复习】第2章 The Internet, the Web, and Electronic Commerce

1. 单选题 (1分) "Wiki" comes from the Hawaiian word for ________.____ A fast B social C small D changeable 正确答案&#xff1a;A 翻译&#xff1a;Wiki来源于夏威夷语中的________。 2. 单选题 (1分) This type of e-commerce often resembles the elec…

WHAT - 高性能和内存安全的 Rust(一)

目录 一、介绍1.1 示例代码1.2 关键特性内存安全零成本抽象&#xff1a;高效性能示例代码&#xff1a;使用迭代器的零成本抽象示例代码&#xff1a;泛型和单态化总结 并发编程&#xff1a;防止数据竞争Rust 并发编程示例Rust 的所有权系统防止数据竞争总结 丰富的类型系统包管理…

2024.06.11校招 实习 内推 面经

绿*泡*泡VX&#xff1a; neituijunsir 交流*裙 &#xff0c;内推/实习/校招汇总表格 1、校招 | 美团2025届北斗计划正式启动&#xff08;内推&#xff09; 校招 | 美团2025届北斗计划正式启动&#xff08;内推&#xff09; 2、实习 | 沃尔沃汽车 Open Day & 实习招聘 …

医学记录 --- 腋下异味

逻辑图地址 症状 病因 汗液分泌旺盛&#xff1a;由于天气炎热、活动出汗、肥胖等因素导致汗液分泌旺盛&#xff0c;可引起腋下有异味表现。在这种情况下&#xff0c;建议保持身体清洁&#xff0c;特别是在炎热和潮湿的环境下。可以使用抗菌洗液、喷雾或霜剂来帮助减少细菌滋…

(done) 关于 GNU/Linux API setenv 的实验

写一个下面的代码来验证 #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h>int main() {// 设置环境变量 MY_VAR 的值为 "hello_world"if (setenv("MY_VAR", "hello_world", 1) ! 0…

【数据挖掘】机器学习中相似性度量方法-闵可夫斯基距离

写在前面&#xff1a; 首先感谢兄弟们的订阅&#xff0c;让我有创作的动力&#xff0c;在创作过程我会尽最大能力&#xff0c;保证作品的质量&#xff0c;如果有问题&#xff0c;可以私信我&#xff0c;让我们携手共进&#xff0c;共创辉煌。 路虽远&#xff0c;行则将至&#…

Android简单登录界面布局设计

<ImageView android:id“id/yxlg” android:layout_marginTop“12dp” android:layout_marginLeft“80dp” android:layout_width“30dp” android:layout_height“30dp” android:background“drawable/net” /> <TextView android:paddingTop“5dp” andr…

Go 语言学习笔记之数组与切片

大家好&#xff0c;我是码农先森。 数组与切片的区别 在 Go 语言中&#xff0c;数组和切片是两种不同的数据结构&#xff0c;它们之间有以下主要区别。 参数长度&#xff1a; 数组&#xff08;Array&#xff09;&#xff1a;数组的长度是固定的&#xff0c;在创建时就需要指…

微信小程序学习(十三):mobx-miniprogram和miniprogram-computed

1、mobx-miniprogram 1.1 介绍 mobx-miniprogram 是针对微信小程序开发的一个简单、高效、轻量级状态管理库&#xff0c;它基于Mobx状态管理框架实现。 使用 mobx-miniprogram 定义管理的状态是响应式的&#xff0c;当状态一旦它改变&#xff0c;所有关联组件都会自动更新相…

图片覆盖攻击

点击劫持的本质是一种视觉欺骗。顺着这个思路&#xff0c;还有一些攻击方法也可以起到类似的作 用&#xff0c;比如图片覆盖。 一名叫 sven.vetsch 的安全研究者最先提出了这种 Cross Site Image Overlaying 攻击&#xff0c;简称 XSIO。sven.vetsch 通过调整图片的 style 使得…

DVWA-XSS(Stored)-beef

用Low Level来测试beef的使用 beef配置 如果kali没有beef的&#xff0c;进行下载 apt install beef-xss使用 beef-xss # 命令方式启动 beef-xss-stop # 命令方式关闭 systemctl start beef-xss.service #开启beefsystemctl stop beef-xss.service #关闭…

前端 CSS 经典:backface-visibility 属性

前言&#xff1a;backface-visibility 属性可以使反转 180deg 的元素隐藏&#xff0c;使用这个属性实现卡片翻转效果 效果 代码实现 <!DOCTYPE html> <html lang"en"><head><meta charset"utf-8" /><meta http-equiv"X-…

记MySQL事务+消息队列引起的问题

问题描述&#xff1a; 先说一下流程&#xff1a;后端保存前端提交的图表信息&#xff0c;然后发送异步消息到消息队列&#xff0c;由下游服务去处理图表信息。 部署项目到服务器&#xff0c;验证项目功能的时候&#xff0c;出现了以下错误&#xff1a;数据库存在数据。下游服…

C++ 音视频传输

目录 一、概述 二、音视频采集 1、视频采集 2、音频采集 三、音视频编码 四、网络传输 五、音视频解码 六、音视频播放 1、视频播放 2、音频播放 七、音视频同步 1. 时间戳管理 2. 缓冲控制 3. 同步策略 3.1 视频为主 3.2 音频为主 3.3 同步点策略 3.4 缓冲区…

Spring Boot+vue社区养老系统(智慧养老平台)

使用技术&#xff1a; springbootvueMySQL 主要功能&#xff1a; 管理员 登录个人资料密码管理, 用户管理:床位类型管理,床位管理,护工管理,老人管理 咨询登记管理&#xff0c;预约登记管理,老人健康信 息管理,费用管理等功能.护工角色包含以下功能: 护工登录&#xff0c;个…

Elastaticsearch与SpringBoot集成的互联网的实战分享

以前有过类似的文章&#xff0c;今天升级版分享重磅内容&#xff0c;Elastaticsearch与SpringBoot集成的互联网的实战。 一、需求分析&#xff1a; 起因是这样的&#xff0c;产品有这样一个需求&#xff1a;数据中的标题、内容、关键词等实现结构化搜索和半结构化搜索、数据时…

自2008年金融危机以来首次,欧洲AAA级CMBS投资者面临亏损

在欧洲预期损失之前&#xff0c;美国AAA级CMBS投资者已经遭受了打击。即便是最高信用等级的投资也不再安全&#xff0c;全球金融系统可能存在一些严重的问题。 历史罕见&#xff0c;最安全的AAA级债权人&#xff0c;在没有发生经济危机的情况下&#xff0c;出现了损失&#xff…

外排序(C语言实现)

前言 本篇博客讲解一下外排序&#xff0c;看这篇排序你的先去看一下&#xff1a;八大经典排序算法-CSDN博客 &#x1f493; 个人主页&#xff1a;普通young man-CSDN博客 ⏩ 文章专栏&#xff1a;排序_普通young man的博客-CSDN博客 若有问题 评论区见&#x1f4dd; &#x1f3…

【数据结构】带头双向循环链表的实现及链表顺序表的区别

目录 一、带头双向循环链表接口实现 连接关系&#xff1a; 创建哨兵位&#xff08;表头&#xff09;&#xff1a; 头插——头删&#xff1a; 尾插——尾删&#xff1a; 查找——打印&#xff1a; 指定位置pos前插入&#xff0c;删除pos位置&#xff1a; 链表销毁&#x…

真实能够生产光刻机地方

目前全球制造光刻机的主要国家包括&#xff1a; 1. **荷兰**&#xff1a;ASML&#xff08;阿斯麦&#xff09;公司是全球最大的光刻机制造商&#xff0c;总部设在荷兰。ASML公司的光刻机被广泛应用于半导体行业&#xff0c;是制造高性能芯片的关键设备之一。 2. **日本**&…