面试题:为什么 wait/notify 必须与 synchronized 一起使用??

文章目录

  • 为什么 java wait/notify 必须与 synchronized 一起使用
  • synchronized是什么
  • synchronized如何实现锁
  • wait/notify
  • 不用synchronized 会怎么样
  • [最终形态] 把lock和obj合一
  • lost wake up


为什么 java wait/notify 必须与 synchronized 一起使用

这个问题就是书本上没怎么讲解,就是告诉我们这样处理,但没有解释为什么这么处理?我也是基于这样的困惑去了解原因。

synchronized是什么

Java中提供了两种实现同步的基础语义:synchronized方法和synchronized块, 看个demo:

public class SyncTest {\\ 1synchronized方法public synchronized void syncMethod(){System.out.println("hello method");}\\ 2synchronizedpublic void syncBlock(){synchronized (this){System.out.println("hello block");}}
}

具体还要区分:

  • 修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁。不同实例对象的访问,是不会形成锁的。

  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
    它具有的特性:

  • 原子性

  • 可见性

  • 有序性

  • 可重入性

synchronized如何实现锁

这样看来synchronized实现的锁是基于class对象来实现的,我们来看看如何实现的,它其实是跟class对象的对象头一起起作用的,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。

其中对象头中有一个Mark Word,这里主要存储对象的hashCode、锁信息或分代年龄或GC标志等信息,把可能的情况列出来大概如下:

图片

其中synchronized就与锁标志位一起作用实现锁。主要分析一下重量级锁也就是通常说synchronized的对象锁,锁标识位为10,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。

每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。

在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的):

ObjectMonitor() {_header       = NULL;_count        = 0; //记录个数_waiters      = 0,_recursions   = 0;_object       = NULL;_owner        = NULL;_WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;FreeNext      = NULL ;_EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;}

上面有2个字段很重要:

  • _WaitSet队列处于wait状态的线程,会被加入到_WaitSet。
  • _EntryList队列处于等待锁block状态的线程,会被加入到该列表。
  • _owner_owner指向持有ObjectMonitor对象的线程

我们来模拟一下进入锁的流程:

1、当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合

2、当线程获取到对象的monitor 后进入 _Owner 区域,并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1

3、若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。

4、若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)

wait/notify

这两个是Java对象都有的属性,表示这个对象的一个等待和通知机制。

不用synchronized 会怎么样

参考其他博客,我们来看看不使用synchronized会怎么样,假设有2个线程,分别做2件事情,T1线程代码逻辑:

while(!条件满足) // line 1
{obj.wait(); // line 2
}
doSomething();

T2线程的代码逻辑:

更改条件为满足; // line 1
obj.notify(); // line 2

多线程环境下没有synchronized,没有锁的情况下可能会出现如下执行顺序情况:

  • T1 line1 满足while 条件
  • T2 line1 执行
  • T2 line2 执行,notify发出去了
  • T1 line2 执行,wait再执行

这样的执行顺序导致了notify通知发出去了,但没有用,已经wait是在之后执行,所以有人说没有保证原子性,就是line1 和line2 是一起执行结束,这个也被称作lost wake up问题。

解决方法就是可以利用synchronized来加锁,于是有人就写了这样的代码:

synchronized(lock)
{while(!条件满足){obj.wait();}doSomething();
}
synchronized(lock)
{更改条件为满足;obj.notify();
}

这样靠锁来做达到目的。但这代码会造成死锁,因为先T1 wait(),再T2 notify();而问题在于T1持有lock后block住了,T2一直无法获得lock,从而永无可能notify()并将T1的block状态解除,就与T1形成了死锁。

所以JVM在实现wait()方法时,一定需要先隐式的释放lock,再block,并且被notify()后从wait()方法返回前,隐式的重新获得了lock后才能继续user code的执行。要做到这点,就需要提供lock引用给obj.wait()方法,否则obj.wait()不知道该隐形释放哪个lock,于是调整之后的结果如下:

synchronized(lock)
{while(!条件满足){obj.wait(lock);// obj.wait(lock)伪实现//   [1] unlock(lock)//   [2] block住自己,等待notify()//   [3] 已被notify(),重新lock(lock)//   [4] obj.wait(lock)方法成功返回}doSomething();
}

[最终形态] 把lock和obj合一

其它线程API如PThread提供wait()函数的签名是类似cond_wait(obj, lock)的,因为同一个lock可以管多个obj条件队列。

而Java内置的锁与条件队列的关系是1:1,所以就直接把obj当成lock来用了。因此此处就不需要额外提供lock,而直接使用obj即可,代码也更简洁:

synchronized(obj)
{while(!条件满足){obj.wait();}doSomething();
}
synchronized(lock)
{更改条件为满足;obj.notify();
}

lost wake up

wait/notify 如果不跟synchronized结合就会造成lost wake up,难以唤醒wait的线程,所以单独使用会有问题。

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

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

相关文章

理解宏任务和微任务:JavaScript 异步编程的必备知识(上)

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

教你5步学会用Llama2:我见过最简单的大模型教学

在这篇博客中,Meta 探讨了使用 Llama 2 的五个步骤,以便使用者在自己的项目中充分利用 Llama 2 的优势。同时详细介绍 Llama 2 的关键概念、设置方法、可用资源,并提供一步步设置和运行 Llama 2 的流程。 Meta 开源的 Llama 2 包括模型权重和…

Java开发项目之KTV点歌系统设计和实现

项目介绍 本系统实现KTV点歌管理的信息化,可以方便管理员进行更加方便快捷的管理。系统的主要使用者分为管理员和普通用户。 管理员功能模块: 个人中心、用户管理、歌曲库管理、歌曲类型管理、点歌信息管理。 普通用户功能模块: 个人中心…

一、CSharp_Basic:什么是.Net平台?什么是.Net FrameWork?什么是C#?

什么是.Net平台? 在了解C#之前,我们应该先了解一下什么是.Net平台。 .Net的诞生 2000年,这时候的微软凭借其Windows操作系统庞大的用户基数,推出了.Net1.0的标准。 也就是实现在Windows平台上面开发和应用程序的概念。我们可以简…

P3 Linux应用编程:系统调用与库函数

前言 🎬 个人主页:ChenPi 🐻推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ 🔥 推荐专栏2: 《Linux C应用编程(概念类)_ChenPi的博客-CSDN博客》✨✨✨ 🛸推荐专栏3: ​​​​​​《 链表_Chen…

BUUCTF [RoarCTF2019]黄金6年 1

BUUCTF:https://buuoj.cn/challenges 题目描述: 得到的 flag 请包上 flag{} 提交。 密文: 下载附件,得到.mp4文件。 attachment 解题思路: 1、浅浅的看了一遍,没发现什么有用的内容。放到Kinovea中,慢倍…

通用plantuml模板头

通用plantuml文件 startuml participant Admin order 0 #87CEFA // 参与者、顺序、颜色 participant Student order 1 #87CEFA participant Teacher order 2 #87CEFA participant TestPlayer order 3 #87CEFA participant Class order 4 #87CEFA participant Subject order …

轻量封装WebGPU渲染系统示例<42>- vsm阴影实现过程(源码)

前向实时渲染vsm阴影实现的主要步骤: 1. 编码深度数据,存到一个rtt中。 2. 纵向和横向执行遮挡信息blur filter sampling, 存到对应的rtt中。 3. 将上一步的结果(rtt)应用到可接收阴影的材质中。 具体代码情况文章最后附上的实现源码。 当前示例源码github地址: …

react native 环境准备

一、必备安装 1、安装node 注意 Node 的版本应大于等于 16,安装完 Node 后建议设置 npm 镜像(淘宝源)以加速后面的过程(或使用科学上网工具)。 node下载地址:Download | Node.js设置淘宝源 npm config s…

GEE:梯度卷积

作者:CSDN @ _养乐多_ 本文将介绍在 Google Earth Engine(GEE)平台上,进行梯度卷积操作的代码框架、核心函数和多种卷积核,比如 Roberts、Prewitt、Sobel、各向同性算子、Compass算子、拉普拉斯算子、不同方向线性检测算子等。 结果如下图所示, 文章目录 一、常用的梯度…

数学建模之典型相关分析

发现新天地,欢迎访问 介绍 典型相关分析(Canonical Correlation analysis)研究两组变量(每组变量中都可能有多个指标)之间相关关系的一种多元统计方法。它能够揭示出两组变量之间的内在联系。 例子 我们要探究观众和业内人士对…

非应届生简历模板13篇

无论您是职场新人还是转行求职者,一份出色的简历都是获得心仪岗位的关键。本文为大家精选了13篇专业的非应届生简历模板,无论您的经验如何,都可以灵活参考借鉴,提升自己的简历质量。让简历脱颖而出,轻松斩获心仪职位&a…

16.字符串处理函数——字符串长度函数

文章目录 前言一、题目描述 二、解题 程序运行代码 总结 前言 本系列为字符串处理函数编程题&#xff0c;点滴成长&#xff0c;一起逆袭。 一、题目描述 二、解题 程序运行代码 #include<stdio.h> #include<string.h> int main() {char str[ ]"0123\0456…

mac安装解压缩rar后缀文件踩坑

mac默认能够解压缩zip后缀的文件&#xff0c;如果是rar后缀的自己需要下载相关的工具解压 下载地址&#xff1a; https://www.rarlab.com/download.htm mac我是因特尔芯片所以下载 x64 然后解压缩文件进入目录 rar中 将可执行文件 rar、unrar 移动到 /usr/local/bin目录下即…

[架构之路-254]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 架构设计 - 全程概述

目录 一、软件架构概述 1.1 什么是软件架构 1.2 为什么需要软件架构设计 1.3 软件架构设计在软件设计中位置 &#xff08;1&#xff09;软件架构设计&#xff08;层次划分、模块划分、职责分工&#xff09;&#xff1a; &#xff08;2&#xff09;软件高层设计、概要设计…

精准长尾关键词批量挖掘工具,长尾关键词挖掘正确使用方法

互联网时代&#xff0c;SEO已然成为网站推广的关键一环。而在SEO的世界里&#xff0c;长尾关键词无疑是一块被广泛忽视却蕴含着巨大潜力的宝地。 什么是长尾关键词 长尾关键词&#xff0c;指的是那些相对不那么热门、搜索量较低但更为具体、更贴近用户真实需求的关键词。与短…

VR 实现 Splash Screen 效果

文章目录 背景官方实现逆向分析 背景 手机 App 在实现 Splash Screen 的时候&#xff0c;目前都有成熟的方案可以参考&#xff0c;但是在做 VR 开发时&#xff0c;要如何实现一个 App 自己的 Splash Screen &#xff0c;下面是我们基于 PICO & OCULUS 进行业务开发时经过探…

Linux:dockerfile编写搭建nginx练习(8)

dockerfile是创建镜像的一种&#xff0c;通过已有镜像的基础上再在上面部署一些别的。 在这个基础镜像上搭建&#xff0c;我这个是一个空的centos镜像 我这里用http的yum仓库存放了nginx和rpm包 创建dockerfile vim Dockerfile写入#设置基础镜像 FROM centos#维护该镜像的用户…

掌握视频剪辑技巧,轻松自定义视频速率,打造个性化出彩视频

你是否曾经因为视频节奏平淡而缺乏吸引力而苦恼&#xff1f;现在&#xff0c;我们为你推荐一款视频批量剪辑工具&#xff0c;让你轻松自定义视频速率&#xff0c;实现出彩个性化视频。 首先第一步&#xff0c;我们要打开好简单批量智剪&#xff0c;并登录账号。 第二步&#x…

从0开始学习JavaScript--JavaScript 箭头函数

JavaScript的现代语法&#xff0c;箭头函数&#xff08;Arrow Functions&#xff09;是一个不可忽视的重要部分。它们不仅提供了更简洁的语法&#xff0c;还改变了函数的作用域规则。在这篇文章中&#xff0c;将深入研究JavaScript箭头函数的概念、语法、用法以及它们与传统函数…