锁的升级打怪:通俗易懂讲解偏向锁、轻量级锁和重量级锁

2024-01-22 18:50

本文主要是介绍锁的升级打怪:通俗易懂讲解偏向锁、轻量级锁和重量级锁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

PART0:

 

PART1:

首先通过一个小例子来解释一下三种锁的区别:

假如家里只有一个碗,当我自己在家时,没有人会和我争碗,这时即为偏向锁状态

当我和女朋友都在家吃饭时,如果女朋友不是很饿,则她会等我吃完再用我的碗去吃饭,这就是轻量级锁状态

当我和女朋友都很饿的时候,这时候就会去争抢这唯一的一个碗(贫穷的我)吃饭,这就是重量级锁状态

 

PART2:

我是一个线程,生活在JVM(Java虚拟机)中, 这一段日子过得有些无聊,整个世界似乎只有这一个人,天天忙着执行代码,想休息一下都很难。

我听说人类写的代码中有些特殊的地方,叫做临界区,比如synchronized修饰的方法或者代码块,他们非常神奇,在同一时刻JVM老大只允许一个线程进入执行。

实际上,老大设置了一把锁,抢到了这把锁就可以执行,否则只能阻塞,等待别人释放锁。

老大说,阻塞就是不用干活了,老老实实地等着就行。

竟然还有这等美事! 赶紧让我阻塞一次吧。

可是老大又说:“每次设置锁我都得和操作系统打交道,请他在内核中维护一个什么Mutex(互斥量)的东西,他还得把你们这些线程阻塞,切换,这可是一笔巨大的费用啊,所以这些锁还是少用为妙。”

我运气也不好,我不知道执行了多少代码,调用了多少函数,竟然一次也没遇到临界区!

我想也许这个程序员编程时不小心,没有考虑多线程并发的情况; 也有可能是这些程序大部分都是无状态的,多少个线程执行都没有问题。

于是我只好一直执行下去, 不知道过了多少天,我激动地发现,一个synchronized修饰的代码块终于出现了:

Account account = ...

synchronized(account){

...临界区的代码...

}

偏向锁

我满心期望别的线程已经进入了代码块,那我就可以阻塞、休息。

即使没有其他线程进入临界区,老大为我申请锁, 也得和操作系统协商什么互斥量,从用户态进入核心态,再从核心态返回用户态,总要花些功夫吧。

可是老大根本没有去找操作系统, 只是看了看这个account对象的所谓“对象头”,其中有个叫做Mark Word的东西,似乎是个什么数据结构, 里边有几个标识位,还有其他数据。

老大随手使用CAS操作把我的线程ID记录到了这个Mark Word当中,修改了标识位,然后告诉我说: 可以了,你现在拥有这把锁了,进去执行代码吧。

我惊奇地说:“老大你不和操作系统协商设置Mutex了?”

老大说:“不用了,你看现在就你一个线程进入了这个代码块,我只要记录下你的线程ID,就表示你拥有这把锁了,不用操作系统介入。”

我获得了锁,开始执行被synchronized包裹的代码块。

等到我第二次执行到这个synchronized的时候,老大只是看了一眼锁对象account的Mark Word就说:“你的线程ID还在,还持有着这个对象的锁,进入临界区执行吧。”

我连喘口气的机会都没有,只好继续执行。

老大说,这叫偏向锁,在没有别的线程竞争的时候,一直偏向我,可以让我一直执行下去。

我是多么期盼来一个新的线程来和我竞争啊!

轻量级锁

很快,机会就来了。

另外一个线程0x3704也要进入这个代码块执行,但是锁对象account 保存的是我的线程ID,他是没法进入临界区的。

我心想,我们两个至少得有一个进入阻塞状态,休息一会儿了。

但是老大还是不去操作系统协商,只是说: 我把这个偏向锁升级一下,变成一个轻量级的锁吧。

老大把锁对象account恢复成无锁状态,在我们俩的栈帧中各自分配了一个空间,叫做Lock Record, 把锁对象account的Mark Word在我们俩这里各自复制了一份,叫做Displaced Mark Word, 这名字真奇怪。

然后把我的Lock Record的地址使用CAS放到了Mark Word当中,并且把锁标志位改为00, 这其实就意味着我也已经获得了这个轻量级的锁了,可以继续进入临界区执行。

0x3704没有获得锁,但还是不阻塞,老大让他自旋几次,等待一会儿。

等到我退出临界区,释放锁的时候,需要把这个Displaced markd word 使用CAS复制回去。接下来他就可以加锁了。

我们两个交替着进入临界区,执行这段代码,相安无事,很少出现真正的竞争。

即使是出现了竞争,想获得锁的线程只要自旋几次,等待一会儿,锁就可能释放了。

很明显,如果没有竞争或者轻度的竞争,轻量级锁仅仅使用CAS操作和Lock record就避免了重量级互斥锁的开销,对JVM老大来说,确实是个好主意。

重量级锁

轻量级锁运行得挺好,我还是没有机会休息,终于有这么一天,0x3704 正在持有锁,在临界区辛苦地执行代码。 我自旋了好多次,0x3704还是没释放锁。 这时候JVM老大说: 自旋次数太多了,太浪费CPU了,接下来升级为重量级锁!

这个重量级锁需要操作系统的帮忙,依赖操作系统底层的Mutex Lock。

只见老大创建了一个monitor 对象, 把这个对象的地址更新到了Mark word当中。

锁升级了!

由于0x3704还在持有锁运行,而我终于进入了梦寐以求的状态:阻塞! 终于可以休息一下了!

仔细一想,老大煞费心机地设置了偏向锁和轻量级锁,就是为了避免阻塞,避免操作系统的介入, 这两种锁无非就是针对这两种情况:

偏向锁: 通常只有一个线程在临界区执行

轻量级锁: 可以有多个线程交替进入临界区,在竞争不激烈的时候,稍微自旋等待一下就能获得锁。

至于重量级锁,也是我最为期待的锁,那就是出现了激烈的竞争,只好让我们去阻塞休息了。

-END-

这篇关于锁的升级打怪:通俗易懂讲解偏向锁、轻量级锁和重量级锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/633914

相关文章

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

使用Python自建轻量级的HTTP调试工具

《使用Python自建轻量级的HTTP调试工具》这篇文章主要为大家详细介绍了如何使用Python自建一个轻量级的HTTP调试工具,文中的示例代码讲解详细,感兴趣的小伙伴可以参考一下... 目录一、为什么需要自建工具二、核心功能设计三、技术选型四、分步实现五、进阶优化技巧六、使用示例七、性能对比八、扩展方向建

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

Python基础文件操作方法超详细讲解(详解版)

《Python基础文件操作方法超详细讲解(详解版)》文件就是操作系统为用户或应用程序提供的一个读写硬盘的虚拟单位,文件的核心操作就是读和写,:本文主要介绍Python基础文件操作方法超详细讲解的相... 目录一、文件操作1. 文件打开与关闭1.1 打开文件1.2 关闭文件2. 访问模式及说明二、文件读写1.

将Mybatis升级为Mybatis-Plus的详细过程

《将Mybatis升级为Mybatis-Plus的详细过程》本文详细介绍了在若依管理系统(v3.8.8)中将MyBatis升级为MyBatis-Plus的过程,旨在提升开发效率,通过本文,开发者可实现... 目录说明流程增加依赖修改配置文件注释掉MyBATisConfig里面的Bean代码生成使用IDEA生

C# WinForms存储过程操作数据库的实例讲解

《C#WinForms存储过程操作数据库的实例讲解》:本文主要介绍C#WinForms存储过程操作数据库的实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、存储过程基础二、C# 调用流程1. 数据库连接配置2. 执行存储过程(增删改)3. 查询数据三、事务处

C++快速排序超详细讲解

《C++快速排序超详细讲解》快速排序是一种高效的排序算法,通过分治法将数组划分为两部分,递归排序,直到整个数组有序,通过代码解析和示例,详细解释了快速排序的工作原理和实现过程,需要的朋友可以参考下... 目录一、快速排序原理二、快速排序标准代码三、代码解析四、使用while循环的快速排序1.代码代码1.由快

MySQL中实现多表查询的操作方法(配sql+实操图+案例巩固 通俗易懂版)

《MySQL中实现多表查询的操作方法(配sql+实操图+案例巩固通俗易懂版)》本文主要讲解了MySQL中的多表查询,包括子查询、笛卡尔积、自连接、多表查询的实现方法以及多列子查询等,通过实际例子和操... 目录复合查询1. 回顾查询基本操作group by 分组having1. 显示部门号为10的部门名,员

linux环境openssl、openssh升级流程

《linux环境openssl、openssh升级流程》该文章详细介绍了在Ubuntu22.04系统上升级OpenSSL和OpenSSH的方法,首先,升级OpenSSL的步骤包括下载最新版本、安装编译... 目录一.升级openssl1.官网下载最新版openssl2.安装编译环境3.下载后解压安装4.备份