Synchronized原理(轻量级锁篇)

article/2025/6/29 15:17:33

Synchronized原理(轻量级锁篇)

简述

介绍

轻量级锁是JDK1.6之中加入的新型锁机制,它名字中的“轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就称为“重量级”锁。首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

轻量级锁能够提升程序性能的依据是“对绝大部分的锁,在整个同步周期内都不存在竞争”,注意这是经验数据。需要了解的是,轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。

引入轻量级锁的目的

JVM的开发者发现在很多情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。

优点

竞争的线程不会阻塞,使用自选,提高程序响应速度。

缺点

如果一直不能获取到锁,长时间的自旋会造成CPU消耗。

适用场景

适用于少量线程竞争锁对象,且线程持有锁的时间不长,追求响应速度的场景。


工作流程

在这里插入图片描述

入口

轻量级锁的进入方式有三种

  • 对象处于未锁定不可偏状态

    在这里插入图片描述

    此状态下对象不能进入偏向锁模式,当有线程尝试获取锁时,会通过轻量级锁的方式获取锁。

  • 对象锁已经偏向于线程(不考虑重偏向场景)

    在这里插入图片描述

    当锁已经偏向于线程,且线程处于锁定状态或处于未锁定但不允许重偏向的情况下,其它的线程尝试获取锁时,会触发偏向锁撤销,然后升级为轻量级或重量级锁定。

  • 对象被轻量级锁定

    在这里插入图片描述

    当对象已经被轻量级锁定的时候,会判断是否是锁重入,如果是重入的话,会记录一条Displaced Mark Word为空的Lock Record。如果不是重入,会膨胀为重量级锁。需要注意的是,即使膨胀为重量级锁,没有获取到锁的线程也不会马上阻塞,而是通过适应性自旋尝试获取锁,当自旋次数达到临界值后,才会阻塞未获取到的线程。JVM认为获取到锁的线程大概率会很快的释放锁,这样做是为了尽可能的避免用户态到内核态的切换。


加锁过程

code 1 :判断对象是否是无锁状态(低三位 = 001),如果是,执行code 2,如果不是,执行code 4

code 2:在栈中建立一个Lock Record,将无锁状态的Mark Word拷贝到锁记录的Displaced Mark Word中,将owner指向当前对象。

code 3:尝试通过CAS 将锁对象的 Mark Word 更新为指向Lock Record的指针,如果更新成功,该线程获取到轻量级锁,并且需要把对象头的Mark Word的低两位改成10(注意这里修改的是对象头的Mark WordLock Record中记录的还是无锁状态的Mark Word);如果更新失败,执行code 4

code 4:对象是轻量级锁定状态,判断对象头的 Mark Word是否指向当前线程的栈帧。如果是,则这次为锁重入,将刚刚建立的Lock Record中的Displaced Mark Word设置为null,记录重入,该线程重入轻量级锁。如果不是,执行code 5

code 5:线程获取轻量级锁失败,锁膨胀为重量级锁,对象头的Mark Word改为指向重量级锁monitor的指针。获取失败的线程不会立即阻塞,先适应性自旋,尝试获取锁。到达临界值后,阻塞该线程,直到被唤醒。

适应性自旋

自适应意味着自旋的时间(次数)不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

图解

无锁状态获取锁

线程执行到同步块时,同步对象处于无锁状态,锁标志位为01,偏向标志位为0,偏向锁被禁用,对象处于无锁态。

在这里插入图片描述

在加锁前,虚拟机需要在当前线程的栈帧中建立锁记录(Lock Record)的空间。Lock Record 中包含一个 _displaced_header 属性,用于存储锁对象的 Mark Word 的拷贝。

在这里插入图片描述

将锁对象的 Mark Word 复制到锁记录中,这个复制过来的记录叫做 Displaced Mark Word。具体来讲,是将 mark word 放到锁记录的 _displaced_header 属性中,将Owner指向当前对象。

在这里插入图片描述

虚拟机使用 CAS 操作尝试将锁对象的 Mark Word 更新为指向锁记录的指针。如果更新成功,这个线程就获得了该对象的锁。

在这里插入图片描述

更新成功后,需要修改原对象头Mark Word中的锁状态标志位为00,目的是告诉其它线程此对象已经被轻量级锁定。

在这里插入图片描述

重入锁

当对象处于加锁状态时,会去检验Mark Word是否指向当前线程的栈帧,如果是则将刚刚建立的Lock Record中的Displaced Mark Word设置为null,记录线程重入锁。

在这里插入图片描述

非重入锁

如果指向的不是当前线程的栈帧则会触发锁膨胀,膨胀为重量级锁。

在这里插入图片描述
Thread2把锁膨胀为重量级锁,Thread1获取到重量级锁,Thread2适应性自旋尝试获取锁,到达临界值后进入等待队列阻塞。
在这里插入图片描述
这里注意执行锁膨胀的线程为Thread2,而非原轻量级锁的持有者Thread1


解锁过程

轻量级锁加锁时有锁重入的可能,同样的,在解锁时也需要判断是否是锁重入解锁。

code 1 :检索当前线程栈中的锁记录空间,从低位往高位找到第一条和此对象有关的Lock Record。加锁时,如果是锁重入,会将 Displaced Mark Word 设置为 null,相应的,在解锁时需要判断Displaced Mark Word是否为 null,如果是,则说明是锁重入解锁,移除onwer的指向,不做替换操作;如果不是,执行code 2

code 2:通过CAS把当前线程栈帧Lock Record中的Displaced Mark Word替换到对象头的Mark Word中去,如果替换成功,则轻量级解锁成功;如果替换失败,则说明发生了锁膨胀,对象现在是重量级锁定状态,执行code 3

code 3:执行重量级锁释放流程(详细过程下篇文章会将),释放重量级锁,同时唤醒被阻塞的线程去获取锁。

图解

非重入释放锁

解锁的思路是使用 CAS 操作把当前线程的栈帧中的 Displaced Mark Word 替换回锁对象中去,如果替换成功,则解锁成功。

在这里插入图片描述

CAS成功

在这里插入图片描述

CAS失败

在这里插入图片描述

轻量级锁未释放前被其它线程尝试获取,此时Mark Word指针已经被替换为指向Monitor,释放锁时CAS会失败,此时需要走重量级解锁流程。

PS:关于重量级解锁流程下篇文章会补充。

重入释放锁

加锁时,如果是锁重入,会将 Displaced Mark Word 设置为 null。相对应地,解锁时,如果判断 Displaced Mark Word 为 null 则说明是锁重入,不做替换操作。

在这里插入图片描述


归纳总结

轻量级锁的适用场景

少量的线程竞争锁,且所有者线程占用锁的事件补偿,追求响应速度的场景。

什么时候会升级为轻量级锁

当对象的偏向模式被关闭、对象处于已偏向已锁定、已偏向未锁定但不支持重偏向的场景下,就会升级为轻量级锁。

什么时候会升级为重量级锁

当竞争产生时就会升级为重量级锁,比如,两个线程同时获取锁,成功的线程会获取到轻量级锁,失败的线程会执行锁膨胀,升级为重量级锁。

轻量级锁怎样实现锁重入

当轻量级锁已经被线程持有,且对象头的Mark Word指向的是当前线程的栈帧时,会把本条Lock RecordDisplaced Mark Word 设置为 null,实现锁重入。当重入解锁时,只需要修改所有者onwer的指向。

轻量级锁是否会自旋

轻量级锁流程不会自旋,自旋发生在产生竞争后,获取失败的线程将锁膨胀为重量级锁。失败的线程不会立刻阻塞,而是先尝试适应性自旋,等待所有者释放锁,当到达临界值后再阻塞。

End

轻量级锁可以看作是偏向锁重偏向的升级版,加入了有锁无锁的状态转换,即使当竞争产生时升级到重量级锁,也不会马上阻塞线程,而是通过适应性自旋来决定是否阻塞,提高了性能。轻量级锁相较于偏向锁来说,简单一些,下一篇会着重介绍重量级锁,以及自旋的线程是怎样进入重量级锁的等待队列的。

参考文献

jdk源码剖析二: 对象内存布局、synchronized终极原理 - 只会一点java - 博客园 (cnblogs.com)

死磕Synchronized底层实现–轻量级锁 · Issue #14 · farmerjohngit/myblog (github.com)


http://chatgpt.dhexx.cn/article/WBbPlipB.shtml

相关文章

轻量级 android模拟器,【分享中控】轻量级中控系统

先上图: 这是一个简单的模拟器本地中控,自恋的说非常好用,它帮助了我成功开发无数款脚本,已经在多台电脑上稳定运行数月。 虽然不算强大,但是足以应付大部分的脚本开发需求。 [hide]注意 1. 使用此软件的前提是安装好 .Net Framework 4.5 运行环境,Win10自带此环境无需安…

浅析轻量级锁

从轻量级锁 来看锁机制。 (目前 上的唯一一张图。= =。 因为有些东西没有图的话 是很难理清楚的 - - )对象是否被某个线程的锁定的依据是, 对象头中记录的信息。 mark word 也叫对象标志词。对象头的信息内容是变化的,变化后是根据不同的锁标志位来描述对应的信息。 比如当…

14种轻量级网络综述 — 主干网络篇

点击上方“3D视觉工坊”,选择“星标” 干货第一时间送达 作者丨VincentLee 来源丨晓飞的算法工程笔记 编辑丨极市平台 导读 早期的卷积神经很少考虑参数量和计算量的问题,由此轻量级网络诞生,其旨在保持模型精度基础上近一步减少模型参数量和…

神经网络学习小记录47——ShuffleNetV2模型的复现详解

神经网络学习小记录47——ShuffleNetV2模型的复现详解 学习前言什么是ShuffleNetV2ShuffleNetV21、所用模块2、网络整体结构 网络实现代码 学习前言 据说ShuffleNetV2比Mobilenet还要厉害,我决定好好学一下。 什么是ShuffleNetV2 这篇是ECCV2018关于轻量级模型的…

一个简单的dos命令实现无限弹窗,卡死电脑

教大家一个简单的dos命令实现无限弹窗,从而实现卡死电脑。 1.新建一个文本文件 2.在该文件里面输入 : start start cmd goto start 3.保存后并把此文件的.txt后缀改为.bat 提示:如果你的计算机不显示后缀拓展名,首先应先让显示拓展名再进行操…

Pycharm制作搞怪弹窗(声音强制最大,屏幕亮度强制最亮,按钮躲避,弹窗炸弹)

Pycharm制作搞怪弹窗(声音强制最大,屏幕亮度强制最亮,按钮躲避,弹窗炸弹) 闲来无聊用python制作了一个搞怪的桌面弹窗程序,惊喜连连哦 运行动图 实现代码: import tkinter as tk import tkinter.font as…

Charles抓包出现弹窗问题或者无法抓包https问题汇总

一、重要问题总结 1、https抓包需要在电脑端和移动端都装上相应的证书!不同的电脑、手机需要的证书可能不一样,如果不能正常工作,建议重新安装。 2、iOS10及以上系统,需要在安装charles证书后 在设置->通用->关于本机->…

python制作恶搞_Pycharm制作搞怪弹窗的实现代码

闲来无聊用python制作了一个搞怪的桌面弹窗程序,惊喜连连哦 运行动图 实现代码: import tkinter as tk import tkinter.font as tkFont # 引入字体模块 import time import sys import pygame import random import threading import win32api import wmi from tkinter.messa…

用Python写一个假的病毒炸弹(整蛊)

病毒炸弹 现在我们用Python来写一个假的病毒炸弹 弹窗实现 import tkinter as tk import random import threading import timedef boom():window tk.Tk()width window.winfo_screenwidth()height window.winfo_screenheight()a random.randrange(0, width)b random.ra…

bat 炸弹升级

转自:http://digi.163.com/15/0320/06/AL4LP0QD0016192R.html 第1页:什么是批处理炸弹? 最近网上流传一个叫做《大哥别杀我》视频纷纷遭到网友模仿,虽然我们都知道视频里出现的人大多都是群众演员,但还是会被视频中各种…

xss完成浏览器视窗炸弹

无聊,,,发个文章。 这个就是很简单的视窗炸弹,放在自己的 xss平台上,执行就会一直打开页面。初学者可以 试一试。 function WindowBomb() { var iCounter 0 // dummy counter while (true) { window.open(“https:/…

Linux fork炸弹以及预防办法

fork炸弹是什么? fork炸弹以极快的速度创建大量进程(进程数呈以2为底数的指数增长趋势),并以此消耗系统分配予进程的可用空间使进程表饱和,而系统在进程表饱和后就无法运行新程序,除非进程表中的某一进程终…

弹窗炸弹恶搞整人

1.创建一个记事本文件 2.编辑文件内容 在文件中输入以下内容并保存 :start start cmd goto start3.实现弹窗炸弹 把文件扩展名改为.bat。 双击执行(慎用),效果如下: 4.解除无线弹窗的方法 方法一: 新建个记事本…

python弹窗炸弹

当我们看某人不爽时,可以用这个: ​ import tkinter as tk import random import threading import timedef dow():window tk.Tk()window.title(你是SB)window.geometry("200x50" "" str(random.randrange(0, window.winfo_scre…

无限弹窗(bat代码 整人恶作剧)

炸弹弹窗,是使用bat制作的一个小程序,效果就是执行程序后会一直不停地弹出窗口,用来恶作剧。下面我们就来看看详细的教程。 打开文件,输入以下代码: :start start cmd goto start 点击文件,选择另存为 把文…

bat代码雨代码流星_怎么制作无限弹窗效果? 限弹窗代码bat文件分享

炸弹弹屏,也可以叫炸弹弹窗,是可以使用txt制作的一个小程序,效果就是执行程序后会一直不停地弹出窗口,就好像炸弹轰炸一样,如图所示,用来恶作剧很好玩。该怎么制作这个无限弹窗效果呢?下面我们就来看看详细的教程。 1、首先,如图所示,我们打开电脑,在桌面鼠标右键,新…

分享森林火灾年鉴统计平台

https://www.yearbookchina.com 可以参观一下,里面有一部分研究人员能用得上的数据

分享统计数据搜集方法

阿关科研统计篇 将近两个月,在统计年鉴中水深火热。终于,形成一套可供后人快速工作的流程,以供大家参考。点名感恩提供帮助的江红蕾博士的引领和帮助。 第一:明确你想获取的统计数据区域 一般而言,历年《中国统计年…

统计年鉴在哪里查找

一、国家统计局 国家统计局>>年度统计公报 (stats.gov.cn) 二、地方统计局 以江苏为例 江苏省人民政府 江苏统计年鉴 (jiangsu.gov.cn) 三、统计年鉴分享平台 可以直接获取国家以及地方统计年鉴,此网站收录的很全。 上海统计年鉴2001 - 统计年鉴分享平台…

统计年鉴分享平台 - 让科研工作者和学生查找数据更方便

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也…