JavaEE之线程 (6)—— 等待方法wait 和唤醒方法notify、notifyAll

2024-05-15 20:52

本文主要是介绍JavaEE之线程 (6)—— 等待方法wait 和唤醒方法notify、notifyAll,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

线程间等待与唤醒机制

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

 球场上的每个运动员都是独立的“执行流” ,可以认为是一个 ”线程“。而完成一个具体的进攻得分动作,则需要多个运动员相互配合,按照一定的顺序执行一定的动作,线程1 先 “传球” ,线程2 才能 "扣篮“

完成上述所提的协调工作, 主要涉及到三个方法:

  1. wait() / wait(long timeout):让当前线程进入等待状态。
  2. notify() / notifyAll(): 唤醒在当前对象上等待的线程
    注意: wait,notify,notifyAll 都是 Object 类的方法。

一、等待方法:wait()

wait 做的事情

  1. 使当前执行代码的线程进行等待. (把线程放到等待队列中)
  2. 释放当前的锁
  3. 满足一定条件时被唤醒, 重新尝试获取这个锁。

注意:wait 要搭配 synchronized 来使用,脱离 synchronized 使用 wait 会直接抛出异常。

wait结束等待的条件

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

我们使用下面的实例代码来演示wait()方法的使用

import javafx.beans.binding.ObjectExpression;/*** @author Zhang* @date 2024/5/715:51* @Description:*/
public class Demo1 {public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object){System.out.println("等待中");object.wait();System.out.println("等待结束");}}
}
------------------------------------------------------------
输出: 等待中

 从上述代码可知,当代码执行到在执行到object.wait()之后就一直等待下去,那么程序肯定不能一直这么等待下去了。这个时候就需要使用到了另外一个方法唤醒的方法notify()。

二、唤醒方法:notify( )

notify 方法是唤醒等待的线程

.

  1. 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的。
  2. 其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
  3. 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁

我们使用下面的实例代码来演示notify()方法的使用:

  1. 创建 WaitTask 类, 对应一个线程, run 内部循环调用 wait;
  2. 创建 NotifyTask 类, 对应另一个线程, 在 run 内部调用一次 notify;
  3. 注意, WaitTask 和 NotifyTask 内部持有同一个 Object locker. WaitTask 和 NotifyTask 要想配合就需要搭配同一个 Object。
/*** @author Zhang* @date 2024/5/716:01* @Description:*/
public class Demo3 {public static void main(String[] args) {Object object = new Object();Thread t1 = new Thread(()->{synchronized(object){System.out.println("wait 之前");try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("wait 之后");}});Thread t2 = new Thread(()->{try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (object){System.out.println("进行通知");object.notify();}});t1.start();;t2.start();}
}
--------------------------------------------------------------wait 之前进行通知wait 之后

三、唤醒方法:notifyAll( )

notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程

static class WaitTask implements Runnable {private Object locker;public WaitTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {while (true) {try {System.out.println("wait 开始");locker.wait();System.out.println("wait 结束");} catch (InterruptedException e) {e.printStackTrace();}}}}
}static class NotifyTask implements Runnable {private Object locker;public NotifyTask(Object locker) {this.locker = locker;}@Overridepublic void run() {synchronized (locker) {System.out.println("notify 开始");locker.notifyAll();System.out.println("notify 结束");}}
}public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(new WaitTask(locker));Thread t2 = new Thread(new NotifyTask(locker));t1.start();Thread.sleep(1000);t2.start();
}---------------------------------
此时可以看到, 调用 notifyAll 能同时唤醒 3 个wait 中的线程

注意: 虽然是同时唤醒 3 个线程,但是这 3 个线程需要竞争锁。所以并不是同时执行,,而仍然是有先有后的执行。

三、经典面试题

nofify 和nofifyAll()的区别

  1. notify 只唤醒等待队列中的一个线程,其他线程还是乖乖等着。
  2. notifyAll 一下全都唤醒,需要这些线程重新竞争锁。

wait和sleep的区别

理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间。
具体区别:

  1. wait 需要搭配 synchronized 使用. sleep 不需要.
  2. wait 是 Object 的方法 sleep 是 Thread 的静态方法.

总结

以上就是今天要讲的内容,本文仅仅简单介绍了等待方法wait、唤醒方法notify、notifyAll。

这篇关于JavaEE之线程 (6)—— 等待方法wait 和唤醒方法notify、notifyAll的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/992895

相关文章

Window Server2016加入AD域的方法步骤

《WindowServer2016加入AD域的方法步骤》:本文主要介绍WindowServer2016加入AD域的方法步骤,包括配置DNS、检测ping通、更改计算机域、输入账号密码、重启服务... 目录一、 准备条件二、配置ServerB加入ServerA的AD域(test.ly)三、查看加入AD域后的变

Window Server2016 AD域的创建的方法步骤

《WindowServer2016AD域的创建的方法步骤》本文主要介绍了WindowServer2016AD域的创建的方法步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、准备条件二、在ServerA服务器中常见AD域管理器:三、创建AD域,域地址为“test.ly”

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服

SpringBoot使用Apache Tika检测敏感信息

《SpringBoot使用ApacheTika检测敏感信息》ApacheTika是一个功能强大的内容分析工具,它能够从多种文件格式中提取文本、元数据以及其他结构化信息,下面我们来看看如何使用Ap... 目录Tika 主要特性1. 多格式支持2. 自动文件类型检测3. 文本和元数据提取4. 支持 OCR(光学

Java内存泄漏问题的排查、优化与最佳实践

《Java内存泄漏问题的排查、优化与最佳实践》在Java开发中,内存泄漏是一个常见且令人头疼的问题,内存泄漏指的是程序在运行过程中,已经不再使用的对象没有被及时释放,从而导致内存占用不断增加,最终... 目录引言1. 什么是内存泄漏?常见的内存泄漏情况2. 如何排查 Java 中的内存泄漏?2.1 使用 J

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.

Spring MVC如何设置响应

《SpringMVC如何设置响应》本文介绍了如何在Spring框架中设置响应,并通过不同的注解返回静态页面、HTML片段和JSON数据,此外,还讲解了如何设置响应的状态码和Header... 目录1. 返回静态页面1.1 Spring 默认扫描路径1.2 @RestController2. 返回 html2

Python中使用defaultdict和Counter的方法

《Python中使用defaultdict和Counter的方法》本文深入探讨了Python中的两个强大工具——defaultdict和Counter,并详细介绍了它们的工作原理、应用场景以及在实际编... 目录引言defaultdict的深入应用什么是defaultdictdefaultdict的工作原理