Java 多线程 --- 线程协作 wait/notify

news2025/2/23 20:08:42

Java 多线程 --- 线程协作 wait/notify

  • wait / notify
  • Object.wait() , Object.notify() / notifyAll()
  • notify 和 wait 的原理
  • notify会导致死锁的问题
  • wait / notify的开销以及问题

wait / notify

  • 在多线程中, 如果程序拿到锁之后, 但是没有满足指定条件而不能继续往下执行, 我们可以将当前线程暂停(进入阻塞状态), 直到满足所需要的条件时再将线程唤醒, 结构如下:
atomic {
	while (条件 不成立) {
		 wait //暂停当前线程
	}
	//执行目标动作
	doAction();
	notify
}
  • 上述操作必须是原子操作. 一个线程因其执行目标动作所需的条件为满足而被暂停的过程就是等待 (Wait)
  • 一个线程使用完critical section. 更新了系统的状态, 使得其他线程所需的保护条件得以,满足的时候唤醒那些被暂停的线程的过程就被称为通知 (Notify)

Object.wait() , Object.notify() / notifyAll()

  • Java中通过 Object.wait() 和 Object.notify() 实现等待和通知.
  • wait和notify都是Object的方法, 也就是每个对象都有wait和notify方法
  • wait()的作用是使正在执行的线程被阻塞
  • notify()的作用是唤醒一个被阻塞的线程.
  • notifyAll()的作用是唤醒全部被阻塞的线程
  • 具体格式如下
sychrnoized(lock) {
	while (条件 不成立) {
		 lock.wait //暂停当前线程
	}
	//执行目标动作
	doAction();
	lock.notify
}

Example:

给你一个类:
public class Foo {
  public void first() { print("first"); }
  public void second() { print("second"); }
  public void third() { print("third"); }
}
三个不同的线程 ABC 将会共用一个 Foo 实例。

线程 A 将会调用 first() 方法
线程 B 将会调用 second() 方法
线程 C 将会调用 third() 方法

请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。
public class Foo {
    
    private int flag = 0;
    //声明一个objetc作为锁
    private Object lock = new Object();
    public Foo() {

    }
    public void first(Runnable printFirst) throws InterruptedException {
        synchronized (lock){
            while( flag != 0){
                //还没有轮到自己运行, 进入阻塞状态. 
                lock.wait();
            }
            printFirst.run();
            flag = 1;
            //唤醒其他在阻塞状态的线程
            lock.notifyAll();
        }
    }
    public void second(Runnable printSecond) throws InterruptedException {
        synchronized (lock){
            while (flag != 1){
                lock.wait();
            }
            printSecond.run();
            flag = 2;
            lock.notifyAll();
        }
    }
    public void third(Runnable printThird) throws InterruptedException {
        synchronized (lock){
            while (flag != 2){
                lock.wait();
            }
            printThird.run();
            flag = 0;
            lock.notifyAll();
        }
    }
}

notify 和 wait 的原理

  • 每个sychronizied锁(也就是内部锁), 都有一个monitor对象
  • monitor对象有三个部分
  • The Owner: 表示目前锁的持有者, 如果为null则表示是无锁状态
  • Entry Set: 记录等待获得相应内部锁的线程. 多个线程申请同一个锁的时候, 只有一个申请者能够成为该锁的持有线程, 其他申请失败者会继续保留在Entry Set.
  • Wait Set: 当一个线程获得锁之后, 因为没有满足某些条件而不得不放弃锁 (调用wait方法). 会被放入Wait Set并进入阻塞状态

在这里插入图片描述

  • 我们知道 Java 虛拟机会为每个锁(也就是对象)维护一个入口集(Entry Set )用于存储申请该对象的内部锁的线程。
  • 此外,Java 虛拟机还会为每个锁(也就是对象) 维护一个被称为等待集(Wait Set )的队列,该队列用于存储该对象上的等待线程。
  • wait方法会将当前线程放进 Wait Set, 并把当前线程变为阻塞状态
  • notify方法会使该对象的Wait Set中的一个任意线程被唤醒。注意此时线程不会释放锁, 要等待临界区运行完毕, 所以notify尽量放在临界区的末尾.
  • 被唤醒的线程仍然会停留在相应对象的Wait Set中,直到该线程再次竞争相应内部锁的时候, Object.wait会使当前线程从其所在的Wait Set中移除.(应该是不管竞争失败或者成功都会被移除, 不过这一点不确定)接着 Object.wait调用就返回了. (具体如伪代码所示)
  • Object. waito/notify()实现的等待/通知中的几个关键动作,包括将当前线程加入等待集, 暂停当前线程, 释放锁以及将唤醒后的等待线程从等待集中移除等,都是在 Obiect.wait() 中实现的.
  • Object. wait() 的部分内部实现相当于如下伪代码:
public void wait () {
	//执行线程必须持有当前对象对应的内部锁
	if (!Thread.holdsLock (this) ) {
		throws new IllegalMonitorStatefxception():
	}
	if (当前对象不在等待集中){
		//将当前线程加入当前对象的等待集中
		addToWaitSet(Thread.currentThread ());
	}
	atomic { 
		//原子操作开始, 释放当前对象的内部锁
		releaselock(this) :
		//阻塞当前线程
		block(Thread. current Thread ());
	}
	
	//再次申请当前对象的内部锁
	acquireLock(this);
	//将当前线程从当前对象的等待集中移除
	removeFromWaitSet(Thread. currentIhread () ) ;
	return;
}

notify会导致死锁的问题

  • 多个线程调用了锁对象的wait()方法,这些线程都会进入到wait set中,等待池中的线程不参与锁竞争。此时只调用一次notify()方法,那么只有一个线程会从wait set进入到entry set竞争资源,并且获得锁资源继续执行接下来的代码。执行完毕后,释放锁。但是由于其它线程都处于等待池中,不会去竞争争夺锁,大家都在等待池中等待通知,故而造成了死锁。除非再次调用notify()或者notifyAll()去触发通知,否则会一直等待下去
  • 如果使用notifyAll则可以避免这种情况, 因为notifyAll会唤醒所有等待线程, 放入entry set中

wait / notify的开销以及问题

To be continued

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

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

相关文章

外网用户打不开公司的网站?web服务器端口映射到公网

我们经常会遇到这样的情景,在公司内部可以打开公司的网站,在家里或者外网却打不开,按照网上的做法,重新启动了服务器和iis,还是不行。许多用户设置了路由器端口映射功能,但是端口映射不成功怎么办&#xff…

2023河南新乡市第二届职业技能大赛“网络安全”项目B模块比赛任务书

2023河南新乡市第二届职业技能大赛“网络安全”项目 比赛任务书一、竞赛时间二、竞赛阶段及内容B-1 Windows操作系统渗透测试B-2 Wireshark数据包分析B-3 CMS网站渗透B-4 未公开B-5 未公开一、竞赛时间 总计:180分钟 二、竞赛阶段及内容 (一&#xff…

嵌入式系统实验——【玄武F103开发板】实现两个LED小灯闪烁

目录一、实验文件main.cstm32f10x.h二、实验思路(一)打开两个LED小灯1.在玄武F103开发板上找到LED0、LED1对应的GPIO控制寄存器2.找到GPIOB、GPIOE的地址3.打开APB2外设时钟的使能寄存器4.对GPIO寄存器进行设置对端口输出寄存器进行设置(二&a…

Go语言基准测试(benchmark)三部曲之三:提高篇

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 -《Go语言基准测试(benchmark)三部曲》已近尾声,经历了《基础篇》和《内存篇》的实战演练,相信您已熟练掌握了基准测试的常…

Spring Security 实现自定义登录和认证(1)

1 SpringSecurity 1.1 导入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId> </dependency>1.2 编写配置类 在spring最新版中禁用了WebSecurityConfigurerAdapter…

《Linux运维实战:基于ansible一键离线部署容器版MySQL一主两从集群(5.6.50、5.7.30、8.0.30)》

一、部署背景 由于业务系统的特殊性&#xff0c;我们需要针对不同的客户环境部署 MySQL主从复制集群&#xff0c;由于大都数用户都是专网环境&#xff0c;无法使用外网&#xff0c;为了更便捷&#xff0c;高效的部署&#xff0c;针对业务系统的特性&#xff0c;我这边编写了基于…

【C++修炼之路】24.哈希应用--位图

每一个不曾起舞的日子都是对生命的辜负 哈希应用--位图哈希应用&#xff1a;位图一.提出问题二.位图概念三.位图代码四.位图应用五.经典问题哈希应用&#xff1a;位图 一.提出问题 问题&#xff1a; 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#x…

信息服务上线渗透检测网络安全检查报告和解决方案3(系统漏洞扫描、相对路径覆盖RPO漏洞、nginx漏洞修复)

系列文章目录 信息服务上线渗透检测网络安全检查报告和解决方案2(安装文件信息泄漏、管理路径泄漏、XSS漏洞、弱口令、逻辑漏洞、终极上传漏洞升级)信息服务上线渗透检测网络安全检查报告和解决方案 文章目录系列文章目录前言一、中风险漏洞相对路径覆盖(RPO)漏洞二、低风险漏…

【2021.12.28】ctf逆向中的迷宫问题(含exe及wp)

【2021.12.28】ctf逆向中的迷宫问题&#xff08;含exe及wp&#xff09; 文章目录【2021.12.28】ctf逆向中的迷宫问题&#xff08;含exe及wp&#xff09;1、迷宫简介&#xff08;1&#xff09;简单例子&#xff08;2&#xff09;一般的迷宫代码2、二维迷宫&#xff08;1&#xf…

LeetCode 460. LFU 缓存 -- 哈希查询+双向链表

LFU 缓存 困难 634 相关企业 请你为 最不经常使用&#xff08;LFU&#xff09;缓存算法设计并实现数据结构。 实现 LFUCache 类&#xff1a; LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象 int get(int key) - 如果键 key 存在于缓存中&#xff0c;则获取键…

IP网络广播对讲系统的先进性及系统功能

IP网络广播对讲系统是一套基于TCP/IP网络的纯数字IP网络对讲机系统。在物理结构上&#xff0c;IP网络广播系统与标准IP网络集成。它不仅真正实现了基于TCP/IP网络的数字音频广播、直播和点播&#xff0c;而且借助TCP/IP网络的优势&#xff0c;突破了传统模拟广播系统的局限性、…

用Python写个猜数字游戏,写游戏难道比玩游戏还好玩(12)

小朋友们好&#xff0c;大朋友们好&#xff01;我是猫妹&#xff0c;一名爱上Python编程的小学生。欢迎和猫妹一起&#xff0c;趣味学Python。今日主题开学差不多有1个月了吧&#xff1f;猫妹刚刚从寒假的玩玩玩模式切换到上学的学学学模式。你呢&#xff1f;这是猫妹的课程表&…

嵌入式中backtrace的使用

大家好&#xff0c;我是bug菌&#xff5e; backtrace主要用于调试程序时&#xff0c;能够打印出程序在运行过程中的函数调用栈&#xff0c;以帮助开发者快速定位程序出现异常或崩溃的原因。 通过backtrace的输出&#xff0c;开发者可以了解程序在哪个函数出现问题&#xff0c…

APM飞控使用动捕等外部定位

本文初次写于2023.03.03&#xff0c;pixhawk飞控应该是刷写了ArduPilot 4.1以上的版本。 机载计算机通过WIFI和vrpn_ros_client获取动捕系统&#xff08;vicon或者nokov&#xff09;的无人机定位数据&#xff08;x&#xff0c;y&#xff0c;z四元数&#xff09;&#xff0c;然…

HMM-维特比算法

HMM-维特比算法&#xff08;viterbi&#xff09;HMM回顾隐马科夫链解法&#xff1a;维特比算法&#xff08;Viterbi&#xff09;HMM回顾 最终的公式可以解释主要分为两个部分&#xff1a; P(xi|yi)&#xff0c;发射概率&#xff0c;字面意思是从一个词性中发射/生成出某一个单…

【Java】Spring Boot下的MVC

文章目录Spring MVC程序开发1. 什么是Spring MVC&#xff1f;1.1 MVC定义1.2 MVC 和 Spring MVC 的关系2. 为什么学习Spring MVC&#xff1f;3. 怎么学习Spring MVC&#xff1f;3.1 Spring MVC的创建和连接3.1.1 创建Spring MVC项目3.1.2 RequestMapping 注解介绍3.1.3 Request…

1 网关介绍

网关介绍 在微服务架构中&#xff0c;一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢&#xff1f;如果没有网关的存在&#xff0c;我们只能在客户端记录每个微服务的地址&#xff0c;然后分别去调用。这样的话会产生很多问题&#xff0c;例如&a…

Threejs 创建的透明球体与模型前后看起来重叠解决方案

创建了球体透明的位置在后面例如 x: -2.2, y: 6, z: 0&#xff0c; 加载的模型在前面x: 2, y: 1, z: 13 ,创建的发光精灵 let spriteMaterial new THREE.SpriteMaterial({ map: spriteTexture, color: object.color, transparent: true, depthWrite: false, //深度写入属性 d…

SpringMVC中JSON数据的设置、RestFul风格

Java知识点总结&#xff1a;想看的可以从这里进入 目录3.4、JSON数据3.4.1、前端使用3.4.2、后端使用1、Jackson2、fastjson3.5、RestFul风格3.5.1、简介3.5.2、使用3.4、JSON数据 3.4.1、前端使用 前端在JavaScript中有封装的JSON对象&#xff0c;可以直接用来操作JSON数据。…

JavaSE之集合篇

文章目录前言一、集合概述集合继承结构图二、Collection接口中常用方法2.1Collection中存放什么元素&#xff1f;2.2常用方法2.3迭代器三、List接口中常用的方法四、ArrayList初始化容量及扩容五、Vector六、Map接口常用方法七、Properties前言 由于在刷题过程中&#xff0c;经…