Java 并发之 wait、notify 机制三问

2024-01-06 16:52

本文主要是介绍Java 并发之 wait、notify 机制三问,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 调用 notify/notifyAll 之后,会立马释放锁吗?

不会。那么什么时候才释放掉锁从而使得在 WaitSet 中的被唤醒的线程能够有机会重新竞争到锁呢?例如:

synchronized(obj) {obj.notify();foo();
}

在这段代码中,就是 foo() 方法执行完毕后,会释放掉 obj 对象的锁,换句话说,也就是 synchronized 代码块执行完毕后,从字节码层面也就是执行了 monitorexit 指令之后。

2. notify、notifyAll 有什么区别

  • notify():Calling notify() causes the JVM to select one thread waiting on that condition queue to wake up.
  • notifyAll():Calling notifyAll() wakes up all the threads waiting on that condition queue.

简而言之,唤醒随机一个还是所有的区别,不过需要注意的是唤醒所有并不是所有等待的线程都会同一时刻恢复执行,还是需要竞争从而那个重新获取到锁的线程可以执行,毕竟同一时刻只可能有一个线程可以持有对象的锁。我们可以通过一段代码来理解这其中的差别:

public class NotificationTest {private volatile boolean go = false;private synchronized void shouldGo() throws InterruptedException {while (!go) {System.out.println(Thread.currentThread() + " is going to wait on this object");wait();System.out.println(Thread.currentThread() + " is woken up");}go = false;}private synchronized void go() {while (!go) {System.out.println(Thread.currentThread() + " is going to notify all or one thread waiting on this object");go = true;notify();}}public static void main(String[] args) throws InterruptedException {NotificationTest test = new NotificationTest();Runnable waitTask = () -> {try {test.shouldGo();} catch (InterruptedException e) {Logger.getLogger(NotificationTest.class.getName()).log(Level.SEVERE, null, e);}System.out.println(Thread.currentThread() + " finished execution");};Runnable notifyTask = () -> {test.go();System.out.println(Thread.currentThread() + " finished execution");};Thread t1 = new Thread(waitTask, "WT1");Thread t2 = new Thread(waitTask, "WT2");Thread t3 = new Thread(waitTask, "WT3");Thread t4 = new Thread(notifyTask, "NT1");t1.start();t2.start();t3.start();Thread.sleep(200);t4.start();}
}

我们来一起看下这段代码。

  1. 有一个共享的 boolean 类型的变量 go,并且使用 volatile 关键字修饰,保证变量的可见性。
  2. 同步方法 shouldGo():wait 的调用以及将 go 置为 false
  3. 同步方法 go():notify 的调用以及将 go 置为 true。
  4. main() 测试方法:3个线程调用 shouldGo(),即3个 wait(),1个线程调用 go(),即 notify()。

运行看下输出结果:
在这里插入图片描述

我们再把上述示例代码中 notify() 改成 notifyAll(),再看输出结果:
在这里插入图片描述
注意红框部分,调用 notifyAll() 把 WaitSet 中的三个线程都唤醒了,被唤醒的第一个线程执行完了 shouldGo() 方法,并且将 go 变量置为 false,这样导致 notifyAll() 唤醒的后面两个线程因为判断条件不满足再次 wait。而 notify() 只随机唤醒了一个线程。

那么我们怎么区分 notify/notifyAll 的使用场景呢?

我们可以借助于 《Java Concurrent In Practice》:

Single notify can be used instead of notifyAll only when both of the following conditions hold:

  • Uniform waiters:Only one condition predicate is associated with the condition queue, and each thread executes the

    same logic upon returning from wait;(只有一个等待条件并且从 wait 放回后执行的逻辑是一样的)

  • One-in, one-out:A notification on the condition variable enables at most one thread to proceed.(一次 notify 只唤醒一个线程)。

只有在同时满足上述两种情况下我们可以使用 notify,剩下的场景我们都应使用 notifyAll,从而保证程序的正确性,虽然会导致无效的通知影响性能。

Most classes don’t meet these requirements, so the prevailing wisdom is to use notifyAll in preference to single notify. While this may be inefficient,it is much easier to ensure that you classes behave correctly when using notifyAll instead of notify.

3. wait、notify 方法为啥定义在 Object 类,而不是 Thread 类

  • 每个 Java 对象都可以作为锁和条件队列
  • 每个对象都可以一个 Monitor 对象与之对应
  • 锁标志放在对象头比在 Thread 类中维护一份列表更合理

参考资料:

https://javarevisited.blogspot.com/2012/10/difference-between-notify-and-notifyall-java-example.html#axzz8Nju8slQg

《Java Concurrent In Practice》

这篇关于Java 并发之 wait、notify 机制三问的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Mac系统下卸载JAVA和JDK的步骤

《Mac系统下卸载JAVA和JDK的步骤》JDK是Java语言的软件开发工具包,它提供了开发和运行Java应用程序所需的工具、库和资源,:本文主要介绍Mac系统下卸载JAVA和JDK的相关资料,需... 目录1. 卸载系统自带的 Java 版本检查当前 Java 版本通过命令卸载系统 Java2. 卸载自定

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Java Spring ApplicationEvent 代码示例解析

《JavaSpringApplicationEvent代码示例解析》本文解析了Spring事件机制,涵盖核心概念(发布-订阅/观察者模式)、代码实现(事件定义、发布、监听)及高级应用(异步处理、... 目录一、Spring 事件机制核心概念1. 事件驱动架构模型2. 核心组件二、代码示例解析1. 事件定义

SpringMVC高效获取JavaBean对象指南

《SpringMVC高效获取JavaBean对象指南》SpringMVC通过数据绑定自动将请求参数映射到JavaBean,支持表单、URL及JSON数据,需用@ModelAttribute、@Requ... 目录Spring MVC 获取 JavaBean 对象指南核心机制:数据绑定实现步骤1. 定义 Ja

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

springboot项目中整合高德地图的实践

《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一:高德开放平台的使用二:创建数据库(我是用的是mysql)三:Springboot所需的依赖(根据你的需求再

spring中的ImportSelector接口示例详解

《spring中的ImportSelector接口示例详解》Spring的ImportSelector接口用于动态选择配置类,实现条件化和模块化配置,关键方法selectImports根据注解信息返回... 目录一、核心作用二、关键方法三、扩展功能四、使用示例五、工作原理六、应用场景七、自定义实现Impor

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项