上一篇通过构建金字塔结构,来从不同的角度,由浅入深的对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次,那么就更改一下处理策略?

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

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

  1. 【从刷面试题到构建知识体系】Java底层-synchronized锁-1

    在技术论坛中,经常看到一种言论:面试造火箭,干活拧螺丝.我们平时写的大部分代码的确是CRDU,再提一个层次,也无非就是揉进去复杂一些的业务逻辑,把一堆的CRDU组合起来. 那么问题来了:我们提倡的研究 ...

  2. 通过面试题学习零散知识:Java面试题整理

     一.如何看待面试题 对于喜欢学习的开发者来说,我们抛开工作和生活的时间,剩余的时间并不多,如果都用于学习的话,也不可能学的下所有感兴趣的技术点,精力也跟不上,我是深感如是.而面试题一般都是零碎的知识 ...

  3. Java知识体系

    Java知识体系 java知识结构.jpg web框架.jpg 计算机课程体系.png 2016-08-19_090929.png 流行的哈希算法生存状况.jpg "JAVA之父" ...

  4. 最强最全的Java后端知识体系

    目录 最全的Java后端知识体系 Java基础 算法和数据结构 Spring相关 数据库相关 方法论 工具清单 文档 @(最强最全的Java后端知识体系) 最全的Java后端知识体系 最全的Java后 ...

  5. github上最全的资源教程-前端涉及的所有知识体系

    前面分享了前端入门资源汇总,今天分享下前端所有的知识体系. 个人站长对个人综合素质要求还是比较高的,要想打造多拉斯自媒体网站,不花点心血是很难成功的,学习前端是必不可少的一个环节, 当然你不一定要成为 ...

  6. github上最全的资源教程-前端涉及的所有知识体系【转】

    github上最全的资源教程-前端涉及的所有知识体系[转自:蓝猫的博客] 综合类 综合类 地址 前端知识体系 http://www.cnblogs.com/sb19871023/p/3894452.h ...

  7. Atitit 知识图谱解决方案:提供完整知识体系架构的搜索与知识结果overview

    Atitit 知识图谱解决方案:提供完整知识体系架构的搜索与知识结果overview   知识图谱的表示和在搜索中的展1 提升Google搜索效果3 1.找到最想要的信息.3 2.提供最全面的摘要.4 ...

  8. android知识体系

    1.Android架构分为4层*应用程序层 Android会同一系列核心应用程序包一起发布,该应用程序包包括email客户端,SMS短消息程序,日历,地图,浏览器,联系人管理程序等.所有的应用程序都是 ...

  9. unity3d所要知道的基础知识体系大纲,可以对照着学习,不定期更新

    本文献给,想踏入3D游戏客户端开发的初学者. 毕业2年,去年开始9月开始转作手机游戏开发,从那时开始到现在一共面的游戏公司12家,其中知名的包括搜狐畅游.掌趣科技.蓝港在线.玩蟹科技.天神互动.乐元素 ...

随机推荐

  1. Maven 梳理 - maven新建web项目提示"javax.servlet.http.HttpServlet" was not found on the Java Build Path

    方法一: <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api&l ...

  2. Flask基础(09)-->请求勾子函数

    什么是请求勾子? 为了让每个视图函数避免编写重复的功能代码,flask提供了通用设施的功能,就是所谓的勾子 那么请求勾子就是,在浏览器请求服务器资源的前后挂载相关的处理函数 请求勾子有什么作用? 作用 ...

  3. 挑战程序设计——迷宫的最短路径(BFS)

    题目详情 Description 给定一个大小为 N * M 的迷宫.迷宫由通道和墙壁组成,每一步可以向邻接的上下左右四格的通道移动.请求出从起点到终点所需的最小步数 限制条件: N,M <= ...

  4. php使用正则表达式提取字符串中尖括号、小括号、中括号、大括号中的字符串

    $str="你好<我>(爱)[北京]{天安门}"; echo f1($str); //返回你好 echo f2($str); //返回我 echo f3($str); ...

  5. 在Linux系统下有一个目录/usr/share/dict/ 这个目录里包含了一个词典的文本文件,我们可以利用这个文件来辨别单词是否为词典中的单词。

    #!/bin/bash s=`cat /usr/share/dict/linux.words` for i in $s; do if [ $1 = $i ];then echo "$1 在字 ...

  6. 快学Scala 第十三课 (类型层级,对象相等性)

    Scala 类型层级: 对象相等性: 和Java一样要重写equals方法和hashcode方法 class Student(val id: Int, val name: String) { over ...

  7. Spark 学习笔记之 MONGODB SPARK CONNECTOR 插入性能测试

    MONGODB SPARK CONNECTOR 测试数据量: 测试结果: 116万数据通过4个表的join,从SQL Server查出,耗时1分多.MongoSparkConnector插入平均耗时: ...

  8. 【Java】JDK安装及环境变量配置

    第一步:下载所需jdk(本次下载Win64位 java1.8...版本) 第二步:点击文件安装,直接下一步到底,成功安装,点击关闭. 第三步:安装完JDK后配置环境变量  计算机→属性→高级系统设置→ ...

  9. 02-12 Logistic(逻辑)回归

    目录 逻辑回归 一.逻辑回归学习目标 二.逻辑回归引入 三.逻辑回归详解 3.1 线性回归与逻辑回归 3.2 二元逻辑回归的假设函数 3.2.1 让步比 3.2.2 Sigmoid函数图像 3.3 二 ...

  10. cookie和session,cookie和web storage

    一.cookie和session cookie和session的共同之处在于:cookie和session都是用来跟踪浏览器用户身份的会话方式. session指的是访问者从到达某个特定页面到离开为止 ...