上一篇通过构建金字塔结构,来从不同的角度,由浅入深的对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. 【Django】一对多表结构

    1.创建project数据库表 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.co ...

  2. Spring 梳理-传递模型数据到视图

    传递model,返回string,不指定返回key package com.jt; import com.myOrg.DeptUserDomain; @Controller @RequestMappi ...

  3. C语言I博客作业02

    这个作业属于那个课程  C语言程序设计I 这个作业要求在哪 https://edu.cnblogs.com/campus/zswxy/CST2019-3/homework/8656 我在这个课程的目标 ...

  4. WebGL简易教程(六):第一个三维示例(使用模型视图投影变换)

    目录 1. 概述 2. 示例:绘制多个三角形 2.1. Triangle_MVPMatrix.html 2.2. Triangle_MVPMatrix.js 2.2.1. 数据加入Z值 2.2.2. ...

  5. 序列标注(BiLSTM-CRF/Lattice LSTM)

    前言 在三大特征提取器中,我们已经接触了LSTM/CNN/Transormer三种特征提取器,这一节我们将介绍如何使用BiLSTM实现序列标注中的命名实体识别任务,以及Lattice-LSTM的模型原 ...

  6. windows下 python 如何安装pygame模块

    本机系统:win7,Pyhon版本: 3.6.0 1. 安装下载python官网 https://www.python.org/ 下载地址 https://www.python.org/downloa ...

  7. Webstorm轻松部署项目至服务器

    wo大前端在开发环境下,需要将项目部署到测试环境,webstorm进行基础配置操作就可实现. 一.在Deployment选项下配置远程服务器地址 点击加号,选择type类型,Name自己填,帮你找到这 ...

  8. 漫谈 GOF 设计模式在 Spring 框架中的实现

    原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」.一群同频者,一起成长,一起精进,打破认知的局限性. 漫谈 GOF 设计模式在 Spring ...

  9. node.js操作数据库之MongoDB+mongoose篇

    前言 node.js的出现,使得用前端语法(javascript)开发后台服务成为可能,越来越多的前端因此因此接触后端,甚至转向全栈发展.后端开发少不了数据库的操作.MongoDB是一个基于分布式文件 ...

  10. 关于ajax提交表单的一些实例及遇到的问题和解决办法

    ajax提交的表单有两种情况: 第一种:input type类型没有file上传文件类型的表单 第二种:input type类型有file上传文件类型的表单 之所以分为两种:是因为原生ajax是不能提 ...