在c#中用mutex类实现线程的互斥_面试官经常问的synchronized实现原理和锁升级过程,你真的了解吗...

本篇文章主要从字节码和JVM底层来分析synchronized实现原理和锁升级过程,其中涉及到了简单认识字节码、对象内部结构以及ObjectMonitor等知识点。

阅读本文之前,如果大家对synchronized关键字的基本使用还不是很了解的话,推荐阅读笔者之前的一遍关于synchronized关键字使用的文章:

synchronized三种使用方式都不知道还想通过面试,门都没有

从字节码角度分析synchronized实现

从JVM规范中可以了解到,无论是synchronized修饰方法(实例/静态方法)还是代码块都是基于进入(entry)和退出(exit)monitor对象来实现,但是两种修饰方式在字节码层面实现上有着很大区别。下面我们通过javap -verbose XXX.class命令查看class文件信息来具体分析两者实现上的差异。

synchronized修饰代码块:

程序源码如下:

76fe88f3999e22f0d76bd9fc5e65aefc.png

源码截图

class文件信息如下:

c1a7681a4c3a6be0861ba528246b30f0.png

class文件信息截图

由上面的class信息可以得知,使用synchronized修饰代码块会在同步代码块之前加monitorenter指令,同时在代码块正常退出(15行)和异常退出(21行)的地方插入monitorexit指令,从而保证monitorenter和monitorexit的成对执行(保证同步代码块执行结束的同时释放锁资源)。可以把monitorenter看作lock.lock(),monitorexit看作lock.unlock(),那么monitorenter和monitorexit可以用更加方便理解的伪代码表示,如下:

b2e5f8795cd83f6acc871a3551ff3b1c.png

伪代码截图

synchronized修饰方法:

程序源码如下:

d18a2d4404056f3542a241581f11e941.png

源码截图

class文件信息如下:

83ba93b885ef2f1c14945de4db8118dd.png

class文件信息截图

由上面的class信息可以得知,synchronized修饰方法并没有通过插入monitorentry和monitorexit指令来实现,而是在方法表结构中的访问标志(access_flags)设置ACC_SYNCHRONIZED标志来实现。线程在执行方法前先判断access_flags是否标记ACC_SYNCHRONIZED,如果标记则在执行方法前先去获取monitor对象,获取成功则执行方法代码且执行完毕后释放monitor对象,获取失败则表示monitor对象被其他线程获取从而阻塞当前线程。

对象头和MarkWord

说到对象头,我们需要先整体了解下对象的内部结构,如下图所示:

d71d0c58c5d8dad864996105e1932d42.png

对象内部结构图

由图可知对象内部结构分为:对象头、实例数据、对齐填充(保证8个字节的倍数)。对象头分为对象标记(markOop)和类元信息(klassOop)。类元信息存储的是指向该对象类元数据(klass)的首地址,4个字节。

对象标记(markOop)是我们重要要介绍的,它存储对象本身运行时的数据,如哈希码、GC标记、锁信息、线程关联等(64位JVM占8个字节,32位JVM占4个字节),称为"Mark Word",存储格式非规定与具体JVM实现有关。

Hotspot JVM中MarkWord存储格式如下:

32位存储格式:

7bc99b68861ecff7f2f07f7fb69abe12.png

32位存储格式

64位存储格式:

3c76fd026f67843aaee4d3095efbe305.png

64位存储格式

由MarkWord存储格式可以了解到JVM可以通过锁标志位来判断锁类型,进而进行处理。注意JDK1.6之前只有重量级锁的,JDK1.6之后才有了偏向锁和轻量级锁,后面锁升级部分会详细讲解。

ObjectMonitor

在JVM的规范中,有这么一些话:“在JVM中,每个对象和类在逻辑上都是和一个监视器相关联的,为了实现监视器的排他性监视能力,JVM为每一个对象和类都关联一个锁,锁住了一个对象,就是获得对象相关联的监视器”。这里的监视器就是指的是ObjectMonitor。

ObjectMonitor在JVM源码中的定义如下:

eaa441626980537f8e5b2eb1e8e1cab5.png

ObjectMonitor的JVM源码

MarkWord中重量级锁指向的重量级指针就是ObjectMonitor对象指针,是基于操作系统互斥(mutex)实现的。

synchronized锁升级和实现原理

synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的。JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁

ba607268e9d6927866f2dfce94ddd168.png

synchronized涉及的锁归类

锁升级的优化是针对于不同同步场景进行的优化,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁,存在竞争时升级为轻量级锁,轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效的,但是如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就需要升级为重量级锁。

db62349a6228f134e0d1796a1062acd6.png

MarkWord结构和锁特征

下面结合上图所示的MarkWord对几种锁类型进行介绍:

  • 无锁:MarkWord标志位01,没有线程执行同步方法/代码块时的状态。
  • 偏向锁:MarkWord标志位01(和无锁标志位一样)。偏向锁是通过在bitfields中通过CAS设置当前正在执行的ThreadID来实现的。假设线程A获取偏向锁执行代码块(即对象头设置了ThreadA_ID),线程A同步块未执行结束时,线程B通过CAS尝试设置ThreadB_ID会失败,因为存在锁竞争情况,这时候就需要升级为轻量级锁。注:偏向锁是针对于不存在资源抢占情况时候使用的锁,如果被synchronized修饰的方法/代码块竞争线程多可以通过禁用偏向锁来减少一步锁升级过程。可以通过JVM参数-XX:-UseBiasedLocking = false来关闭偏向锁。
  • 轻量级锁:MarkWord标志位00。轻量级锁是采用自旋锁的方式来实现的,自旋锁分为固定次数自旋锁和自适应自旋锁。 轻量级锁是针对竞争锁对象线程不多且线程持有锁时间不长的场景, 因为阻塞线程需要CPU从用户态转到内核态,代价很大,如果一个刚刚阻塞不久就被释放代价有大。具体实现和升级为重量级锁过程:线程A获取轻量级锁时会把对象头中的MarkWord复制一份到线程A的栈帧中创建用于存储锁记录的空间DisplacedMarkWord,然后使用CAS将对象头中的内容替换成线程A存储DisplacedMarkWord的地址。如果这时候出现线程B来获取锁,线程B也跟线程A同样复制对象头的MarkWord到自己的DisplacedMarkWord中,如果线程A锁还没释放,这时候那么线程B的CAS操作会失败,会继续自旋,当然不可能让线程B一直自旋下去,自旋到一定次数(固定次数/自适应)就会升级为重量级锁。
  • 重量级锁:通过对象内部监视器(monitor)实现,monitor本质前面也提到了是基于操作系统互斥(mutex)实现的,操作系统实现线程之间切换需要从用户态到内核态切换,成本非常高。

注:锁只可以升级不可以降级,但是偏向锁可以被重置为无锁状态。

最后,附上一张关于synchronized锁升级流程图(很全面很牛):

a7d46a9f12789ac7bfdcebdc5fa3cbe6.png

作者收藏很久的,synchronized锁升级流程图(很全面很牛)

注:由于文章中上传的图片会被压缩,清晰度受到影响,可以关注并私信作者"锁升级"获取synchronized锁升级流程图(高清版)。

END

笔者是一位热爱互联网、热爱互联网技术、热于分享的年轻人,如果您跟我一样,我愿意成为您的朋友,分享每一个有价值的知识给您。喜欢作者的同学,点赞+转发+关注哦!

点赞+转发+关注,私信作者“读书笔记”即可获得BAT大厂面试资料、高级架构师VIP视频课程等高质量技术资料。

407e959d06d86b87b830b93e76d7d436.png

BAT等一线互联网面试资料和VIP高级架构师视频

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/473051.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TensorFlow 2.x GPU版在conda虚拟环境下安装步骤

先下载安装驱动:https://www.nvidia.cn/Download/index.aspx?langcn,版本要求 WSL cuda 驱动 https://developer.nvidia.com/cuda/wsl 下载安装 anaconda,管理虚拟环境:https://www.anaconda.com/products/individual&#xff0…

如何通过网络将文件传输到嵌入式设备_嵌入式系统 Boot Loader技术内幕,带你完全了解Boot Loader...

一个嵌入式 Linux 系统从软件的角度看通常可以分为四个层次:1. 引导加载程序。包括固化在固件(firmware)中的 boot 代码(可选),和 Boot Loader 两大部分。2. Linux 内核。特定于嵌入式板子的定制内核以及内核的启动参数。3. 文件系统。包括根文件系统和建…

python获取当前路径的方法_Python获取脚本所在目录的正确方法【转】

原博文 2015-09-24 10:21 − 1.以前的方法如果是要获得程序运行的当前目录所在位置,那么可以使用os模块的os.getcwd()函数。如果是要获得当前执行的脚本的所在目录位置,那么需要使用sys模块的sys.path[0]变量或者sys.argv[0]来获得。实际上sys.path是Pyt…

TensorFlow 2.0 - Checkpoint 保存变量、TensorBoard 训练可视化

文章目录1. Checkpoint 保存变量2. TensorBoard 训练过程可视化学习于:简单粗暴 TensorFlow 2 1. Checkpoint 保存变量 tf.train.Checkpoint 可以保存 tf.keras.optimizer 、 tf.Variable 、 tf.keras.Layer 、 tf.keras.Model path "./checkp.ckpt" …

coturn的负载均衡特性_高性能负载均衡

单服务器无论如何优化,无论采用多好的硬件,总会有一个性能天花板,当单服务器的性能无法满足业务需求时,就需要设计高性能集群来提升系统整体的处理性能。高性能集群的本质很简单,通过增加更多的服务器来提升系统整体的…

LintCode MySQL 1928. 网课上课情况分析 I

文章目录1. 题目2. 解题1. 题目 online_class_situation 表展示了一些同学上网课的行为活动。 每行数据记录了一名同学在退出网课之前,当天使用同一台设备登录课程后听过的课程数目(可能是0个)。 写一条 SQL 语句,查询每位同学第…

python输入十个数输出最大值_python输入十个数如何输出最大值

python输入十个数输出最大值的方法:1、如果是整数的话,使用函数【a, b, c map(int, input().split())】;2、使用函数【Xinput().split()】。 相关免费学习推荐:python视频教程 python输入十个数输出最大值的方法: 第一…

LintCode MySQL 1921. 从不充值的玩家(where not in)

文章目录1. 题目2. 解题1. 题目 描述 A game database contains two tables, player table and recharge table. Write a SQL query to find all players who never recharge. 样例 https://www.lintcode.com/problem/players-who-never-recharge/description 2. 解题 -- …

古风一棵桃花树简笔画_广东有个现实版的“桃花源”,藏于秘境之中,最适合情侣来度假!...

上学时,初闻“芳草鲜美,落英缤纷”,并没有多大感触。直到后来长大离家,每每为生活奔波劳累时,为工作琐碎忧心费神时,才骤然明了当年五柳先生所描绘的“桃花源”该是多少人的脑中所想、心中所向……原以为这…

clob和blob是不是可以进行模糊查询_你知道什么是 MySQL 的模糊查询?

作者 | luanhz责编 | 郭芮本文对MySQL中几种常用的模糊搜索方式进行了介绍,包括LIKE通配符、RegExp正则匹配、内置字符串函数以及全文索引,最后给出了性能对比。引言MySQL根据不同的应用场景,支持的模糊搜索方式有多种,例如应用最…

LintCode 1917. 切割剩余金属

文章目录1. 题目2. 解题1. 题目 描述 金属棒工厂的厂长拥有 n 根多余的金属棒。 当地的一个承包商提出,只要所有的棒材具有相同的长度(用 saleLength 表示棒材的长度),就将金属棒工厂的剩余棒材全部购买。 厂长可以通过将每根棒…

太原理工电子信焦工程_电气工程及其自动化专业毕业后做什么工作?近几年就业和收入怎样...

本文内容为各大高校往届大学生真实的现身说法内容,但因为是往届,每年该专业的大学情况可能会发生略微变化,所以部分内容较今年,明年甚至以后几年,实际情况可能会略有不同但是对于本专业的相关信息还是非常有参考价值的…

怎么查看linux日志里请求量最高的url访问最多的_实用的Linux高级命令,开发运维都要懂!...

在运维的坑里摸爬滚打好几年了,我还记得我刚开始的时候,我只会使用一些简单的命令,写脚本的时候,也是要多简单有多简单,所以有时候写出来的脚本又长又臭。像一些高级点的命令,比如说 Xargs 命令、管道命令、…

ggplot2箱式图两两比较_第十九章_使用ggplot2进行高级绘图

介绍ggplot2包使用形状、颜色和尺寸来对多元数据进行可视化用刻面图比较各组自定义ggplot2图19.1 R中的四种图形系统基础gridlatticeggplot2(用的较多)gghub需要的R包ggpolt2gridExtra(可以拼图)car19.2 ggplot2介绍library(ggplot2)ggplot(datamtcars, aes(xwt, ympg)) geom_p…

LeetCode 1742. 盒子中小球的最大数量

文章目录1. 题目2. 解题1. 题目 你在一家生产小球的玩具厂工作,有 n 个小球,编号从 lowLimit 开始,到 highLimit 结束(包括 lowLimit 和 highLimit ,即 n highLimit - lowLimit 1)。 另有无限数量的盒子…

bash shell命令(1)

本文地址:http://www.cnblogs.com/archimedes/p/bash-shell1.html,转载请注明源地址。 ls命令 ls用来列出目录的内容,它是用户最常用的命令之一,ls命令的格式为: ls[选项][目录名或文件名] 选项的主要参数:…

LeetCode 1743. 从相邻元素对还原数组(拓扑排序)

文章目录1. 题目2. 解题1. 题目 存在一个由 n 个不同元素组成的整数数组 nums ,但你已经记不清具体内容。 好在你还记得 nums 中的每一对相邻元素。 给你一个二维整数数组 adjacentPairs ,大小为 n - 1 ,其中每个 adjacentPairs[i] [ui, v…

BP神经网络算法学习

BP(Back Propagation)网络是1986年由Rumelhart和McCelland为首的科学家小组提出,是一种按误差逆传播算法训练的多层前馈网络,是眼下应用最广泛的神经网络模型之中的一个。BP网络能学习和存贮大量的输入-输出模式映射关系&#xff…

无向图的深度优先遍历非递归_【数据结构图(一)】什么是图

一、什么是“图”(Graph) 表示“多对多”的关系包含一组顶点:通常用 V (Vertex) 表示顶点集合一组边:通常用 E (Edge) 表示边的集合无向边:(v, w) 有向边:不考虑重边和自回路二、抽象数据类型定义类型名称:图(Graph)数…

LeetCode 1744. 你能在你最喜欢的那天吃到你最喜欢的糖果吗?(前缀和)

文章目录1. 题目2. 解题1. 题目 给你一个下标从 0 开始的正整数数组 candiesCount ,其中 candiesCount[i] 表示你拥有的第 i 类糖果的数目。 同时给你一个二维数组 queries ,其中 queries[i] [favoriteTypei, favoriteDayi, dailyCapi] 。 你按照如下…