【从刷面试题到构建知识体系】Java底层-synchronized锁-2偏向锁篇
上一篇通过构建金字塔结构,来从不同的角度,由浅入深的对synchronized关键字做了介绍,
快速跳转:https://www.cnblogs.com/xyang/p/11631866.html
本文将从底层实现的各个“组件”着手,详细拆解其工作原理。
本文会分为以下4节内容:
第一节:介绍MarkWord和LockRecord两种数据结构,该知识点是理解synchronized关键字底层原理的关键。
第二节:分析偏向锁加锁解锁时机和过程
一.先来了解两种数据结构,你应该了解这些知识点
1.MarkWord:在锁的使用过程中会对锁对象作出相应的操作
在HotSpot虚拟机中,Java对象在内存中存储的布局,分为三个部分:对象头,实例数据,对齐填充。
本文重点关注对象头。
对象头又划分为2或3部分,具体包括:
- MarkWord(后文简称MW,后续详细介绍)
- 类型指针:指向这个对象所属的类的元数据(klass)的指针
- 最后这一部分比较特殊,只有在对象是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.加锁过程
步骤一:
- LR记录赋值:在当前线程的栈中,申请一个LR,把obj指向锁对象
步骤二:如图中所示,线程T1,执行到同步代码,尝试加偏向锁,首先会做【偏向锁是否可用】的判断:
- 锁对象的对象头MW区域后3个bit位的值是101。特别需要注意:如果是001,是无锁状态,代表偏向锁不可用,会走加轻量级锁流程。
- ThreadId值:
- 如果ThreadId=0,代表无任何线程持有该对象的偏向锁,可以执行加锁操作,进入加锁流程;
- 如果ThreadId!=0,就判断其值是否是当前线程的ID,分两种情况:如果是,直接锁重入,不再重复加锁。如果否,说明是其他线程(图中T2)已获得了同步锁,进入“第三步”锁竞争流程。
- epoch值:对象所属Class里也会维护一个epoch值,这里我们简称为cEpoch,对该值的判断,可能会导致两种操作:
- 如果epoch<cEpoch,且ThreadId!=0,说明发生过批量重偏向,当前锁对象已被“释放”了。此时进行“重偏向”(里说的释放并非真正意义的释放,而是隐含着一层意思:当前线程已经执行完同步块,且在某次重偏向操作中,也检测到这一点,不再维护epoch的最新值,这样新的线程认为此时该偏向锁,可以加锁,直接CAS修改ThreadId即可)。
- 如果ThreadId==0,因为偏向锁没有显示的撤销修改ThreadId过程,说明肯定是初始状态,那么epoch值也肯定是初始状态0,此时直接进行加锁操作。
可加锁状态的MW内容如下图所示:
|
锁类型 |
25bit |
4bit |
1bit |
2bit |
|
|
23bit |
2bit |
是否偏向锁 |
锁标志位 |
||
|
偏向锁 |
ThreadId==0 |
epoch==n |
分代年龄 |
1 |
01 |
以上三个点都判断通过,进入“第二步”,加锁流程
第二步:通过CAS原子操作,把T1的ThreadId写入MW。执行结果有两种情况:
- 写入成功,获得偏向锁,进入同步代码块执行同步逻辑。
- 写入失败,表明在第一步判断和CAS操作之间,有其他线程已获得了锁。走锁竞争逻辑。
2.解锁过程
当前线程执行完同步代码块后,进行解锁,解锁操作比较简单,仅仅将栈中的最近一条LR中的obj赋值为null。这里需要注意,MW中的threadId并不会做修改。

3.锁竞争处理流程
持有锁的线程T2并不会在发现竞争的第一时间就直接撤销锁,或者升级锁,而是执行到安全点后再处理。
- 此时如果当前线程已执行完同步块代码且线程已不存活,将会撤销锁,将锁对象恢复至无锁状态,然后进入锁升级逻辑。
- 如果当前线程同步块还未执行完或者线程依然存活,将会走锁升级流程,升级为轻量级锁,且升级完后T2继续持有轻量级锁,继续执行同步代码。

ps:怎么判断是否还在执行同步代码呢?遍历栈中的RL,如果都为null,代表锁已全部释放。
4.批量重偏向和批量撤销
有这样一种场景:如果我们预判竞争不多,大部分情况下是单一线程执行同步块,开启了偏向锁。但是在实际使用环境中,出现了大量的竞争,这时候怎么办呢?停机重新配置参数?恐怕不是最好的方案。如果是我们来设计这个这个Synchronized锁,肯定也会做一些兜底策略。比如这样来做,当某一事件发生了N次,那么就更改一下处理策略?
是的,基本思想差不多,只不过更完善,暂时留一个悬念,在下次揭晓。
【从刷面试题到构建知识体系】Java底层-synchronized锁-2偏向锁篇的更多相关文章
- 【从刷面试题到构建知识体系】Java底层-synchronized锁-1
在技术论坛中,经常看到一种言论:面试造火箭,干活拧螺丝.我们平时写的大部分代码的确是CRDU,再提一个层次,也无非就是揉进去复杂一些的业务逻辑,把一堆的CRDU组合起来. 那么问题来了:我们提倡的研究 ...
- 通过面试题学习零散知识:Java面试题整理
一.如何看待面试题 对于喜欢学习的开发者来说,我们抛开工作和生活的时间,剩余的时间并不多,如果都用于学习的话,也不可能学的下所有感兴趣的技术点,精力也跟不上,我是深感如是.而面试题一般都是零碎的知识 ...
- Java知识体系
Java知识体系 java知识结构.jpg web框架.jpg 计算机课程体系.png 2016-08-19_090929.png 流行的哈希算法生存状况.jpg "JAVA之父" ...
- 最强最全的Java后端知识体系
目录 最全的Java后端知识体系 Java基础 算法和数据结构 Spring相关 数据库相关 方法论 工具清单 文档 @(最强最全的Java后端知识体系) 最全的Java后端知识体系 最全的Java后 ...
- github上最全的资源教程-前端涉及的所有知识体系
前面分享了前端入门资源汇总,今天分享下前端所有的知识体系. 个人站长对个人综合素质要求还是比较高的,要想打造多拉斯自媒体网站,不花点心血是很难成功的,学习前端是必不可少的一个环节, 当然你不一定要成为 ...
- github上最全的资源教程-前端涉及的所有知识体系【转】
github上最全的资源教程-前端涉及的所有知识体系[转自:蓝猫的博客] 综合类 综合类 地址 前端知识体系 http://www.cnblogs.com/sb19871023/p/3894452.h ...
- Atitit 知识图谱解决方案:提供完整知识体系架构的搜索与知识结果overview
Atitit 知识图谱解决方案:提供完整知识体系架构的搜索与知识结果overview 知识图谱的表示和在搜索中的展1 提升Google搜索效果3 1.找到最想要的信息.3 2.提供最全面的摘要.4 ...
- android知识体系
1.Android架构分为4层*应用程序层 Android会同一系列核心应用程序包一起发布,该应用程序包包括email客户端,SMS短消息程序,日历,地图,浏览器,联系人管理程序等.所有的应用程序都是 ...
- unity3d所要知道的基础知识体系大纲,可以对照着学习,不定期更新
本文献给,想踏入3D游戏客户端开发的初学者. 毕业2年,去年开始9月开始转作手机游戏开发,从那时开始到现在一共面的游戏公司12家,其中知名的包括搜狐畅游.掌趣科技.蓝港在线.玩蟹科技.天神互动.乐元素 ...
随机推荐
- bugku—Web_Writeup
Bugku_Web_Writeup Writeup略显粗糙~~ 部分Web题没有得到最后的flag~只是有了一个简单的思路~~ Web1: 如上,打开题目答题网址后就会弹出一张图片,看图片就可以发现是 ...
- 最清晰的RESTFUL理解
Restful理解 API(Application Programming Interface),顾名思义:是一组编程接口规范,客户端与服务端通过请求响应进行数据通信.REST(Representat ...
- Java 学习笔记之 异常法停止线程
异常法停止线程: public class RealInterruptThread extends Thread { @Override public void run() { try { for ( ...
- ELK 学习笔记之 Logstash之output配置
Logstash之output配置: 输出到file 配置conf: input{ file{ path => "/usr/local/logstash-5.6.1/bin/spark ...
- Kafka 学习笔记之 Producer/Consumer (Scala)
既然Kafka使用Scala写的,最近也在慢慢学习Scala的语法,虽然还比较生疏,但是还是想尝试下用Scala实现Producer和Consumer,并且用HashPartitioner实现消息根据 ...
- 【JavaScript】使用纯JS实现多张图片的懒加载(附源码)
一.效果图如下 上面的效果图,效果需求如下 1.还没加载图片的时候,默认显示加载图片背景图 2.刚开始进入页面,自动加载第一屏幕的图片 3.下拉界面,当一张图片容器完全显露出屏幕,即刻加载图片,替换背 ...
- Java读源码之Object
前言 JDK版本: 1.8 最近想看看jdk源码提高下技术深度(比较闲),万物皆对象,虽然Object大多native方法但还是很重要的. 源码 package java.lang; /** * Ja ...
- ES(Elastic Search)update操作设置无 docment时进行insert
最近使用一套数据加工中间工具,查看es操作中的update操作.其中方法命名为updateOrInsert.但是没发现代码中有ES的insert方法调用.于是仔细分析了代码逻辑. 经过一路追溯,直至E ...
- A-01 最小二乘法
目录 最小二乘法 一.最小二乘法--代数法 二.最小二乘法--矩阵法 三.最小二乘法优缺点 3.1 优点 3.2 缺点 更新.更全的<机器学习>的更新网站,更有python.go.数据结构 ...
- html、css以及javascript的注释方式
HTML:<!--注释内容 -->; CSS:/* 注释内容*/ JS: //注 释内容; 或者块/* 注释内容 */, sublime中注释方法:选中注释内容+ctrl+/ , ...