【从刷面试题到构建知识体系】Java底层-synchronized锁-2偏向锁篇

上一篇通过构建金字塔结构,来从不同的角度,由浅入深的对synchronized关键字做了介绍,

快速跳转:https://www.cnblogs.com/xyang/p/11631866.html

本文将从底层实现的各个“组件”着手,详细拆解其工作原理。

本文会分为以下4节内容:

  第一节:介绍MarkWord和LockRecord两种数据结构,该知识点是理解synchronized关键字底层原理的关键。

  第二节:分析偏向锁加锁解锁时机和过程

一.先来了解两种数据结构,你应该了解这些知识点

1.MarkWord:在锁的使用过程中会对锁对象作出相应的操作

在HotSpot虚拟机中,Java对象在内存中存储的布局,分为三个部分:对象头,实例数据,对齐填充。

本文重点关注对象头。

对象头又划分为2或3部分,具体包括:

  1. MarkWord(后文简称MW,后续详细介绍)
  2. 类型指针:指向这个对象所属的类的元数据(klass)的指针
  3. 最后这一部分比较特殊,只有在对象是Java数组时才会存在,记录的是数组的长度。为什么要存在这个记录呢?我们知道,在普通Java对象中,我们可以通过读取对象所属类的元数据,计算出对象的大小。而数组是无法做到的,于是借助这块区域来记录。

本文重点关注MW区域

MW是一块固定大小内存区域,在32位虚拟机中是32个bit,对应的,64位虚拟机中是64个bit。本文以32位虚拟机为例分析。

我们从直观上理解,所谓的头信息,一般都是用来记载一些不易变的信息,例如在http请求头中的各种头信息。在对象头中也是如此,例如hashcode。在JVM虚拟机中为了解决存储空间开销,对象头的MW大小已经固定。那么,要存储的信息有比较多,包括且不限于:锁标志位、GC信息、锁相关信息,总大小远远超出32bit,怎么办呢?

共享存储区域,在不同的时刻,根据需求存储需要的信息。

请参考下图:


锁类型


25bit


4bit


1bit


2bit

 
23bit


2bit


是否偏向锁


锁标志位


无锁


对象hashcode


分代年龄


0


01


偏向锁


线程ID


epoch


分代年龄


1


01


轻量级锁


指向栈中锁记录的指针


00


重量级锁


指向互斥量


10


GC标记



11

说明:两个标志位最多只能标识4个状态,那么剩下一个怎么办?共享。无锁和偏向锁共享01状态,他们两个的区分

2.LockRecord:

在当前线程的栈中申请LR(LockRecord简称,下同),主要包含两部分,第一步部分可以用于存放MW的副本;第二部分obj,用于指向锁对象。

上述两者的关系用下图表示:

二.偏向锁怎么工作

在对象创建的时候,MW会有一个初始态,要么是无锁态,要么是初始偏向锁态(ThreadId、epoch值都为初始值0)。程序员的世界不存在二义性,最终总会选一个,选择的依据是虚拟机的配置参数,在JDK1.6以后,默认是开启的,如果要禁用掉:-XX:-UseBiasedLocking。

什么时候需要禁用呢?如果能确认程序在大多数情况下,都存在多线程竞争,那么就可以禁用掉偏向锁。没必要每次都走一遍偏向锁->轻量级锁->重量级锁的完整升级流程。

1.先放一张图,直观的描述偏向锁的加锁、解锁、撤销基本流程

2.加锁过程

步骤一:

  1. LR记录赋值:在当前线程的栈中,申请一个LR,把obj指向锁对象

步骤二:如图中所示,线程T1,执行到同步代码,尝试加偏向锁,首先会做【偏向锁是否可用】的判断:

  1. 锁对象的对象头MW区域后3个bit位的值是101。特别需要注意:如果是001,是无锁状态,代表偏向锁不可用,会走加轻量级锁流程。
  2. ThreadId值:
    1. 如果ThreadId=0,代表无任何线程持有该对象的偏向锁,可以执行加锁操作,进入加锁流程;
    2. 如果ThreadId!=0,就判断其值是否是当前线程的ID,分两种情况:如果是,直接锁重入,不再重复加锁。如果否,说明是其他线程(图中T2)已获得了同步锁,进入“第三步”锁竞争流程。
  3. epoch值:对象所属Class里也会维护一个epoch值,这里我们简称为cEpoch,对该值的判断,可能会导致两种操作:
    1. 如果epoch<cEpoch,且ThreadId!=0,说明发生过批量重偏向,当前锁对象已被“释放”了。此时进行“重偏向”(里说的释放并非真正意义的释放,而是隐含着一层意思:当前线程已经执行完同步块,且在某次重偏向操作中,也检测到这一点,不再维护epoch的最新值,这样新的线程认为此时该偏向锁,可以加锁,直接CAS修改ThreadId即可)。
    2. 如果ThreadId==0,因为偏向锁没有显示的撤销修改ThreadId过程,说明肯定是初始状态,那么epoch值也肯定是初始状态0,此时直接进行加锁操作。

    可加锁状态的MW内容如下图所示:

    


锁类型


25bit


4bit


1bit


2bit


23bit


2bit


是否偏向锁


锁标志位


偏向锁


ThreadId==0


epoch==n


分代年龄


1


01

    以上三个点都判断通过,进入“第二步”,加锁流程

第二步:通过CAS原子操作,把T1的ThreadId写入MW。执行结果有两种情况:

  1. 写入成功,获得偏向锁,进入同步代码块执行同步逻辑。
  2. 写入失败,表明在第一步判断和CAS操作之间,有其他线程已获得了锁。走锁竞争逻辑。

2.解锁过程

当前线程执行完同步代码块后,进行解锁,解锁操作比较简单,仅仅将栈中的最近一条LR中的obj赋值为null。这里需要注意,MW中的threadId并不会做修改。

3.锁竞争处理流程

  持有锁的线程T2并不会在发现竞争的第一时间就直接撤销锁,或者升级锁,而是执行到安全点后再处理。

  1. 此时如果当前线程已执行完同步块代码且线程已不存活,将会撤销锁,将锁对象恢复至无锁状态,然后进入锁升级逻辑。
  2. 如果当前线程同步块还未执行完或者线程依然存活,将会走锁升级流程,升级为轻量级锁,且升级完后T2继续持有轻量级锁,继续执行同步代码。

  ps:怎么判断是否还在执行同步代码呢?遍历栈中的RL,如果都为null,代表锁已全部释放。

4.批量重偏向和批量撤销

有这样一种场景:如果我们预判竞争不多,大部分情况下是单一线程执行同步块,开启了偏向锁。但是在实际使用环境中,出现了大量的竞争,这时候怎么办呢?停机重新配置参数?恐怕不是最好的方案。如果是我们来设计这个这个Synchronized锁,肯定也会做一些兜底策略。比如这样来做,当某一事件发生了N次,那么就更改一下处理策略?

是的,基本思想差不多,只不过更完善,暂时留一个悬念,在下次揭晓。

原文地址:https://www.cnblogs.com/xyang/p/11698549.html

时间: 2024-08-29 15:30:15

【从刷面试题到构建知识体系】Java底层-synchronized锁-2偏向锁篇的相关文章

“如何构建知识体系”的思考

对于知识体系体系的构建,一直都是学习和工作中,非常重要的问题. 这段时间看了很多这方面的文章,其中京东架构师 李玥 的一篇文章,感觉讲的很好,对我也有一定的启发. 从这篇文章中,可以总结出,对于技术问题的理解和解决,可以从下面几个方面看: 焦虑的现象.原来与解决方法 焦虑的现象:做技术会感到焦虑,不懂的技术,新技术太多 焦虑的来源:对某些不好的事情过度担心而产生的一种烦躁情绪.担心更多是来源于"看不清"或"未知" 解决焦虑的办法:给一个导航,能看到自己所处的位置,前

使用印象笔记的“大坑”,构建知识体系

第一次使用印象笔记这东西的时候,觉得好方便,可以把各个角落的信息收集在一起. 也学习了各种印象笔记的使用技巧,不断的改进优化使用方法,不断的添加有用的信息. 直到现在,不知不觉笔记已经上千条了,不停的学习,不愿放过任何渠道收集来的知识. 不断的添加信息到印象笔记,有那么多需要注意学习的,感觉压力山大,没时间学,好挫败. 看着这一大堆笔记,感觉自己好像图书馆管理员,整理整理再整理. 他们只是零散的信息,看起来"有用"的信息而已,与我有什么关系呢?并不能解决什么问题. 互联网时代,我们一天

认知,构建个人的知识体系(上)

1.前言 本文将聊聊我对构建个人知识体系的一些想法,主要是为了提升自我认知.从个人经历开始,谈谈对知识的划分,也就是一个是什么,为什么的过程. 2.缘起 把时间回到一年前,那时候我工作快一年了,得益于前面的一些努力,工作比较顺利.特别是技术上,没有遇到太多过无法解决的问题.同时也开始迷茫,工作难道就是这个轻松的样子?三五年之后那不是很无趣,该怎么办? 想找到这个问题的答案,而最好的方式莫过于,亲自去了解那些三五年工作经验的人是怎么的样子. 因此从那时候起,关注了不少来公司面试的人的简历,也有过几

技术人,为什么需要构建知识图谱

这个时代,信息极大丰富,人每时每刻都被各种各样的知识.信息轰炸着.如何有效的选择对自己有价值的知识,如何构建一个独属于自己的知识体系并让它为自己创造价值,变得越来越重要.只有知识经过了你的选择和应用,内化为自己的隐性经验,纳入到你的知识体系中,才能真正地为你创造财富. 怎样才能建立自己的知识体系呢? 选择方向,设定目标 学习.实践.输出.内化 同主题扩散 运用工具整理记录知识图谱 迭代与更新知识图谱 有目标,才有知识体系 如果只能选择一个方向,你希望在什么方向上做到出类拔萃? 对这个问题的思考,

构建知识图谱,让自己更值钱

现在这个时代,信息极大丰富,知识浩如烟海,每个人每时每刻都被各种各样的知识.信息轰炸着,如何有效的选择对自己有价值的知识,如何构建一个独属于自己的知识体系并让它为自己创造价值,变得越来越重要. 泛泛地讲,知识就是财富,信息就是财富,具体到个体身上,只有知识经过了你的选择和应用,内化为自己的隐性经验,纳入到你的知识体系中,才能真正地为你创造财富. 那么,怎样才能建立自己的知识体系呢? 构建个人知识系统的一般要经历下面这些过程: 选择方向,设定目标 学习.实践.输出.内化 同主题扩散 运用工具整理记

.NET开发知识体系

记得几年前写过一篇关于.NET开发方面的知识总结,最近园子里也有人总结.NET方面的知识体系,所以就想把之前那篇文章翻出来,结合现在的理解加以更新,如下. 参考资料: 1,我之前的那篇文章:让我们都建立自己的知识树吧 2,王福朋的,自己总结的web前端知识体系大全[欢迎补充] 3,灵感之源的两篇文章:.NET技术大系概览 (迄今为止最全的.NET技术栈),Web前端开发大系概览 (前端开发技术栈)

如何构建自己的知识体系-碎片化学习的骗局

现在年底流行知识跨年,今年毫无例外的知识跨年又刷屏了,其中最出名的莫过于罗振宇的"时间的朋友“跨年演讲及吴晓波的跨年演讲,不瞒大家说,我曾经是吴晓波频道.罗辑思维忠实的读者,每天早上会准时收听罗辑思维的60秒语音及相关文章,也会每周观看吴晓波频道音视频及相关文章,在每次读完相关文章及观看完音视频以后,感觉收货颇多:为此还向自己的父母推荐过,曾经在脑子里还想象过,如果我这样长期的坚持下去,一定会成为一个特别优秀的人:经过自己长时间的学习,确实扩展了自己的知识面,知道了更多的名词,但是感觉整体的认知

个人知识体系构建

关于知识体系构建很多年前读过一篇文章<学那么多东西,不如先建立自己的知识体系>,当时就将这片文章画出了思维导图来,然后就是记住了这篇文章而已,算是一种学过了的状态:浅尝即止.其实这种就是大多数人所常见的学习的状态和节点,也是没有能够做到知行合一的结合,所以很多时候我们好像认为自己懂了,其实我们真的没有搞明白到底是怎么回事,也就是说:学习知识后没有将这些知识进行实践,融入到自己的实践与生活.工作中,并不算真正的学会. 我是在2017年和周老师学习敏捷个人的课程,在年前参与了敏捷个人的练习过后,重

一、如何构建自己的知识体系

构建可用的知识体系就要读书,书是有体系结构的. 何谓知识体系? 技术与技巧包括:计算机基础理论. 计算机模型:内存/IO/时钟/CPU... 专项技术领域:算法.数据挖掘.数据管理.智能推荐.搜索...... 语言与工具:语言与相关体系.开发工具,分析工具,代码管理工具.HTML/CSS/Ajax.常用框架与第三方类库. 调试与测试:调试方法和哲学.定位问题.BUG管理工具.单元测试.集成测试.性能测试.安全测试.兼容性测试与方法.JS/Ajax测试与方法.服务层测试.Web层测试. 网络与系统