sychronized原理(嚼碎了喂版)

先说一下心得吧,我们知道硬软不分家,在学习底层原理的时候我们不需要死扣到底,没必要把硬件方面全吃透,点到为止,学到能够帮助理解代码即可,我们的目标是写出高性能的代码,而不是创造出硬软一体化高性能套件。不要一学底层就一股子牛劲死磕,至少我们现在不应该这样,莫要本末倒置。(好吧其实是我在学的时候有点转牛角尖了,一直问ai问题,仿佛是想把整个计算机领域吃透一般)希望这篇文章对大家有帮助,认真看完哦!欢迎指出理解有误的地方!!!

先看看 Java 中 new 一个对象会有哪些信息被创建出来

在 HotSpot 虚拟机中,一个对象在堆内存的存储布局可以划分为三部分(以 64 位操作系统为例,不用在意 32 位下的情况,他会被淘汰…) :

对象头在这里插入图片描述

实例数据:存类中声明的成员信息,如 int a = 2(占 4 字节)

对其填充:对象存储必须按 8 字节对齐(64 位系统的默认配置),如果 对象头+实例数据 所占的 bit 不是 8 的倍数,如为 65bit(随便举例),那么只这个部分会填充 7bit,变为 72bit(9 字节)。目的是:提高内存访问效率:CPU 读取内存时,对齐的数据能减少总线周期(如 64 位 CPU 一次读 8 字节),避免伪共享(False Sharing):对齐后,不同对象不会共享同一缓存行。(硬件知识)

再来看看 synchronized 到底是什么模样

人人都说 sy 重,重在哪里呢?

重量级锁的实现是基于 monitor 机制的,那就先谈谈 Monitor(管程)

他是操作系统层面的一个东西,提供了一种结构化的方式来管理共享数据和并发访问,其核心组成为 互斥锁条件变量,是由操作系统的一些指令来控制的(这里不过多展开,因为我不会)

再说说 JVM 层面的具体实现:

JVM 中的 Monitor 是通过 C++实现的 ObjectMonitor 对象,当升级为重量级锁时底层会创建这个对象,并把它的地址放在对应 Java 对象的 mark word 中(再去看看上面的图),根据这个地址进行之后的一系列操作,字段有:

  • _owner: 指向当前持有锁的线程。

  • _count: 记录锁的重入次数。

  • _EntryList: 等待获取锁的线程队列(阻塞队列)。

  • _WaitSet: 调用 wait() 方法后进入等待状态的线程队列。

再来说说为什么 sy 重:

在获取、释放等锁相关的操作时,本质上都是操作这个 ObjectMonitor 对象,线程的阻塞、唤醒、调度都是操作系统的职责,必须通过操作系统内核来完成(调用内核中的底层方法)这就牵扯到了从用户态内核态的切换

用户态:

  • 用户态的程序没有权限直接操作其他线程的状态(如从运行状态切换到阻塞状态)。

  • 用户态程序也无法直接访问和修改操作系统的线程调度队列。

  • 这些操作涉及到对底层系统资源的访问和管理,是操作系统的核心功能。

而这个切换是非常销毁后资源的,会执行很多指令来完成这项操作

可以粗略的认为:用户态是 CPU 执行应用程序代码(如 Java 字节码、Python 解释器代码),而内核态是 CPU 执行操作系统内核代码(如文件读写,内存分配等底层操作),但要注意的是 用户态和内核态的切换本质是 CPU 特权级别的变化,而不是仅仅是“谁在运行代码”,所以说可以“粗略认为”,帮助理解即可

  • 系统调用是用户态程序进入内核态的唯一方式。

  • 进入内核态需要保存当前用户线程的上下文(寄存器状态、程序计数器等,记录执行到了哪里方便下次回来接着执行),然后切换到内核的代码执行。

  • 从内核态返回用户态时,需要恢复用户线程的上下文。

  • 这个保存和恢复上下文的过程就是 上下文切换 (Context Switch),它是有开销的,通常比用户态的指令执行慢几个数量级。(注意这里讨论的是用户态和内核态的上下文切换,有人可能会想到线程的上下文切换,他们不是同一个概念,但相同点是都会造成额外的开销)

再来说说重量级锁的操作流程:

当一个线程尝试获取一个对象的 Monitor 时:

  1. 如果 _owner 为空,线程成功获取锁,设置 _owner 为自身,_count 为 1。

  2. 如果 _owner 是当前线程,_count 加 1(重入)。

  3. 如果 _owner 是其他线程,当前线程进入 _EntryList 阻塞等待。

当一个线程释放 Monitor 时:

  1. _count 减 1。

  2. 如果 _count 变为 0,释放锁,_owner 置空。

  3. 然后从 _EntryList_WaitSet 中唤醒一个或多个线程,让它们有机会竞争锁。

这里插播一下线程的几个重要的状态(操作系统层面)

BLOCKED、WAITTING(TIME)都不会消耗 CPU 资源,在进入这个状态时会自动释放占用的资源

只有 RUNNING 才会消耗资源

而 RUNNABLE 知识代表这个线程可以开始干活了,但是还没有活干,等待 CPU 时间片分给他活,是不消耗资源的

而在 Java 层面

没有 RUNNING 状态

RUNNABLE 状态就包含了 RUNNABLE 与 RUNNING

所以说,sy 重的核心原因是:线程的阻塞、唤醒和调度是操作系统的职责,必须通过内核来完成。

既然我们知道了导致 sy 重的原因是线程阻塞引起的,解决的方法当然就是不让他阻塞咯,那么怎么让他不阻塞捏?

无锁化编程 CAS 应运而生,挑起了重担,他通过让线程自旋尝试获取锁的方法来规避去阻塞等待的方式。

有人可能会问:CAS 自旋不是会造成 CPU 空转吗?这不也在浪费资源吗?

是的,CAS 自旋消耗 CPU 资源,用户态与内核态之间的切换亦会浪费资源。但仍选择优化为 CAS 自旋的核心目的是 在“短时间锁竞争”和“长时间锁竞争”之间找到性能平衡(你想,如果在一个线程第一次尝试获取锁失败之后锁立马被释放了,然而他却去了阻塞队列…这得多造孽呀,如果再坚持一下的话…或许我和她的结果就会不一样了…😭,这样看适当自旋一下还是非常好的)

sy 采用的是先自旋,再阻塞的策略。(她一直不搭理我,我也不能一直舔吧…我也是有尊严的!!!)

“先自旋后阻塞”是一种 折中策略,通过 动态适应锁竞争情况,在 低延迟高吞吐 之间取得平衡。

  • 自旋:为短期锁竞争优化响应速度。

  • 阻塞:为长期锁竞争优化系统资源利用率。

一句话,先自旋,不行再阻塞(翻译:先舔舔,不行咱就走呗,等着找下家~**)**

了解了整体思路,最后来看看 sy 中 偏向锁、轻量级锁以及重量级锁的具体实现:

按照我们上面的分析,产物应该就是轻量级锁(CAS)咯,我猜官方的想法是既然要走不阻塞这条路那就干脆极端一点来个无锁判断(偏向)得了,所以就又加了偏向锁,干脆 CAS 操作都不做了,很彻底!

偏向锁:

单线程竞争,当线程 A 第一次竞争到锁时,通过修改 MarkWord 中的偏向线程 ID、偏向模式。如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步(JVM 不会执行任何额外的同步操作(如 CAS、系统调用、内核态切换等),而是直接允许线程访问临界区。) .

什么时候升级为轻量级锁呢?

  • 调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销,(轻量级锁会在锁记录中记录 hashCode,重量级锁会在 Monitor 中记录 hashCode)

  • 当有另外一个线程逐步来竞争锁的时候,就不能再使用偏向锁了,要升级为轻量级锁,使用的是等到竞争出现才释放锁的机制

  • 竞争线程尝试 CAS 更新对象头失败,会等到全局安全点(此时偏向锁对应的 ThreadID 线程不会执行任何代码)撤销偏向锁,同时检查持有偏向锁的线程是否还在执行:

    • 第一个线程正在执行 Synchronized 方法(处于同步块),它还没有执行完,其他线程来抢夺,该偏向锁会被取消掉并出现锁升级,此时轻量级锁由原来持有偏向锁的线程持有,继续执行同步代码块,而正在竞争的线程会自动进入自旋等待获得该轻量级锁

    • 第一个线程执行完 Synchronized(退出同步块),则将对象头设置为无锁状态并撤销偏向锁,重新偏向。

题外话:Java15 以后逐步废弃偏向锁,需要手动开启------->维护成本高

轻量级锁

JVM 会为每个线程在当前线程的栈帧中创建用于存储锁记录(Lock Record)的空间,官方称为 DisplacedMarkWord。若一个线程获得锁时发现是轻量级锁,会把锁的 MarkWord 复制到自己的 DisplacedMarkWord 里面。然后线程尝试用 CAS 将锁的 MarkWord 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示 MarkWord 已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁。

如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数

自旋 CAS:不断尝试去获取锁,能不升级就不往上捅,尽量不要阻塞(升级为重量级锁)

轻量级锁的释放

在释放锁时,当前线程会使用 CAS 操作将 Displaced MarkWord 的内容复制回锁的 MarkWord 里面。如果没有发生竞争,那么这个复制的操作会成功。如果有其他线程因为自旋多次导致轻量级锁升级成了重量级锁,那么 CAS 操作会失败,此时会进入重量级锁解锁流程

自旋一定程度和次数(Java8 之后是自适应自旋锁------意味着自旋的次数不是固定不变的):

  • 线程如果自旋成功了,那下次自旋的最大次数会增加,因为 JVM 认为既然上次成功了,那么这一次也大概率会成功

  • 如果很少会自选成功,那么下次会减少自旋的次数甚至不自旋,避免 CPU 空转

轻量锁和偏向锁的区别:

  • 争夺轻量锁失败时,自旋尝试抢占锁

  • 轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁

重量级锁

当线程尝试获取轻量级锁失败后,进入锁膨胀,创建 ObjectMonitor 对象并将锁中的 mark word 字段移入到该 Monitor 对象中,将该对象地址放入 mark word 中,接下来的流程在上文已经讲过了

锁释放时通过 Monitor 地址找到对象,将 owner 设置为 null,唤醒 EntyList 中 BLOCKED 线程去抢锁

补充一下 wait/notify 原理:

·Owner 线程发现条件不满足,调用 wait 方法,即可进入 WaitSet 变为 WAITING 状态

·BLOCKED 和 WAITING 的线程都处于阻塞状态,不占用 CPU 时间片

·BLOCKED 线程会在 Owner 线程释放锁时唤醒

·WAITING 线程会在 Owner 线程调用 notify 或 notifyAll 时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入

EntryList 重新竞争

完结撒花🎉

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

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

相关文章

Ngrok 配置:实现 Uniapp 前后端项目内网穿透

文章目录 一、下载并安装 ngrok二、配置 ngrok Authtoken三、启动本地 uniapp 项目四、使用 ngrok 暴露本地服务五、通过公网 URL 访问项目六、后端API项目的穿透问题排查 (uni-app 后端 API 示例)交互流程图示 七、ngrok Web 界面 (本地监控)八、停止 ngrok总结 ngrok 是一款…

k8s灰度发布

基于 Traefik 的加权灰度发布-腾讯云开发者社区-腾讯云 Traefik | Traefik | v1.7 Releases traefik/traefik GitHub 从上面连接下载后上传到harbor虚拟机 vagrant upload /C/Users/HP280/Downloads/traefik 下载配置文件 wget -c http://raw.githubusercontent.com/conta…

win10-django项目与mysql的基本增删改查

以下都是在win10系统下,django项目的orm框架对本地mysql的表的操作 models.py----->即表对应的类所在的位置 在表里新增数据 1.引入表对应的在models.py中的类class 2.在views.py中使用函数:类名.objects.create(字段名值,字段名"值"。。。…

`ParameterizedType` 和 `TypeVariable` 的区别

在 Java 的泛型系统中,ParameterizedType 和 TypeVariable 是两个不同的类型表示,它们都属于 java.lang.reflect.Type 接口的子接口。两者都在反射(Reflection)中用于描述泛型信息,但用途和含义不同。 🌟 一…

PR-2021

推荐深蓝学院的《深度神经网络加速:cuDNN 与 TensorRT》,课程面向就业,细致讲解CUDA运算的理论支撑与实践,学完可以系统化掌握CUDA基础编程知识以及TensorRT实战,并且能够利用GPU开发高性能、高并发的软件系统&#xf…

unity使用ZXing.Net生成二维码

下载链接 https://github.com/micjahn/ZXing.Net 放到Plugins下即可使用

Ubuntu 编译SRS和ZLMediaKit用于视频推拉流

SRS实现视频的rtmp webrtc推流 ZLMediaKit编译生成MediaServer实现rtsp推流 SRS指定某个固定网卡,修改程序后重新编译 打开SRS-4.0.0/trunk/src/app/srs_app_rtc_server.cpp,在 232 行后面添加: ZLMediaKit编译后文件存放在ZLMediakit/rele…

如何备考GRE?

1.引言 GRE和雅思不太相同,首先GRE是美国人的考试,思维方式和很多细节和英系雅思不一样。所以底层逻辑上我觉得有点区别。 难度方面,我感觉GRE不容易考低分,但考高分较难。雅思就不一样了不仅上限难突破,下限还容易6…

uniapp|商品列表加入购物车实现抛物线动画效果、上下左右抛入、多端兼容(H5、APP、微信小程序)

以uniapp框架为基础,详细解析商品列表加入购物车抛物线动画的实现方案。通过动态获取商品点击位置与购物车坐标,结合CSS过渡动画模拟抛物线轨迹,实现从商品图到购物车图标的动态效果。 目录 核心实现原理坐标动态计算抛物线轨迹模拟​动画元素控制代码实现详解模板层设计脚本…

React中使用openLayer画地图

OpenLayers(简称ol)是一个‌开源的WebGIS前端开发库‌,基于JavaScript实现,主要用于在网页中嵌入动态二维地图。 官方网站: https://openlayers.org 中文官网: https://openlayers.vip 大家可以去参考学习…

WHAT - 缓存命中 Cache Hit 和缓存未命中 Cache Miss

文章目录 一、什么是缓存命中?二、前端开发要知道哪些缓存机制(以及命中条件)?1. 浏览器缓存(主要针对静态资源)常见的缓存位置关键 HTTP 头字段(决定命中与否) 2. 前端应用层缓存&a…

10 个可靠的 Android 文件传输应用程序

Android 文件传输是 Android 用户的常见需求。我们经常需要将文件从一台 Android 设备传输到 PC 或 Mac。但我们怎样才能做到这一点呢?俗话说,工欲善其事,必先利其器。因此,首先了解 10 个锋利的 Android 文件传输应用程序&#x…

AlphaEvolve:LLM驱动的算法进化革命与科学发现新范式

AlphaEvolve:LLM驱动的算法进化革命与科学发现新范式 本文聚焦Google DeepMind最新发布的AlphaEvolve,探讨其如何通过LLM与进化算法的结合,在数学难题突破、计算基础设施优化等领域实现革命性进展。从48次乘法优化44矩阵相乘到数据中心资源利…

Java大师成长计划之第24天:Spring生态与微服务架构之分布式配置与API网关

📢 友情提示: 本文由银河易创AI(https://ai.eaigx.com)平台gpt-4-turbo模型辅助创作完成,旨在提供灵感参考与技术分享,文中关键数据、代码与结论建议通过官方渠道验证。 在微服务架构中,如何管理…

eSwitch manager 简介

eSwitch manager 的定义和作用 eSwitch manager 通常指的是能够配置和管理 eSwitch(嵌入式交换机)的实体或接口。在 NVIDIA/Mellanox 的网络架构中,Physical Function(PF)在 switchdev 模式下充当 eSwitch manager&am…

最新开源 TEN VAD 与 Turn Detection 让 Voice Agent 对话更拟人 | 社区来稿

关键词:对话式 AI | 语音智能体 | Voice Agent | VAD | 轮次检测 | 声网 | TEN GPT-4o 所展示对话式 AI 的新高度,正一步步把我们在电影《Her》中看到的 AI 语音体验变成现实。AI 的语音交互正在变得更丰富、更流畅、更易用,成为构建多模态智…

AI实践用例---日程规划(通用日程管理文件ICS)灵感踩坑日常

我是一位践行独立开发者之路的菜鸟开发者。 由于执行力较差,常常有很多想法但是很多时候没有去践行。 所以我有了让大模型为我生成日程安排的想法,这确实可以,很简单。只需要将你的想法告诉ai就行了。 例如: 发给AI的提示词: 我想你帮我对,嗯,未来的一年做一个嗯,大…

大疆无人机​​DRC 链路

在大疆上云API中,​​DRC 链路​​通常指 ​​Device-Cloud Remote Control Link(设备-云端远程控制链路)​​,它是无人机(或设备)与云端服务之间建立的​​实时控制与数据传输通道​​,用于实现…

tomcat一闪而过,按任意键继续以及控制台中文乱码问题

问题描述 今天在打开tomcat,启动startup.bat程序时 tomcat直接闪退,后面查找资料后发现,可以通过编辑startup.bat文件内容,在最后一行加入pause即可让程序不会因为异常而终止退出 这样方便查看tomcat所爆出的错误: 然后,我明确看到我的tomcat启动程序显示如下的内容,没有明确…

中大型水闸安全监测系统解决方案

一、方案概述 中大型水闸作为水利工程的重要组成部分,承担着调节水位、控制水流、防洪排涝等多重功能,在防洪减灾、水资源配置、生态环境改善等方面发挥着巨大作用。然而,由于历史原因,许多水闸存在建设标准偏低、质量较差、配套设…