百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

看完你就明白的锁系列之锁的状态 锁的四种状态

yuyutoo 2024-10-12 01:22 2 浏览 0 评论

前面两篇文章我介绍了一下

  • 看完你就应该能明白的悲观锁和乐观锁
  • 看完你就明白的锁系列之自旋锁

看完你就会知道,线程如果锁住了某个资源,致使其他线程无法访问的这种锁被称为悲观锁,相反,线程不锁住资源的锁被称为乐观锁,而自旋锁是基于 CAS 机制实现的,CAS又是乐观锁的一种实现,那么对于锁来说,多个线程同步访问某个资源的流程细节是否一样呢?换句话说,在多线程同步访问某个资源时,锁的状态会如何变化呢?本篇文章来探讨一下。

锁状态的分类

Java 语言专门针对 synchronized 关键字设置了四种状态,它们分别是:无锁、偏向锁、轻量级锁和重量级锁,但是在了解这些锁之前还需要先了解一下 Java 对象头和 Monitor。

Java 对象头

我们知道 synchronized 是悲观锁,在操作同步之前需要给资源加锁,这把锁就是对象头里面的,而Java 对象头又是什么呢?我们以 Hotspot 虚拟机为例,Hopspot 对象头主要包括两部分数据:Mark Word(标记字段)Klass Pointer(类型指针)

Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。

Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

在32位虚拟机和64位虚拟机的 Mark Word 所占用的字节大小不一样,32位虚拟机的 Mark Word 和 Klass Pointer 分别占用 32bits 的字节,而 64位虚拟机的 Mark Word 和 Klass Pointer 占用了64bits 的字节,下面我们以 32位虚拟机为例,来看一下其 Mark Word 的字节具体是如何分配的

用中文翻译过来就是

  • 无状态也就是无锁的时候,对象头开辟 25bit 的空间用来存储对象的 hashcode ,4bit 用于存放分代年龄,1bit 用来存放是否偏向锁的标识位,2bit 用来存放锁标识位为01
  • 偏向锁 中划分更细,还是开辟25bit 的空间,其中23bit 用来存放线程ID,2bit 用来存放 epoch,4bit 存放分代年龄,1bit 存放是否偏向锁标识, 0表示无锁,1表示偏向锁,锁的标识位还是01
  • 轻量级锁中直接开辟 30bit 的空间存放指向栈中锁记录的指针,2bit 存放锁的标志位,其标志位为00
  • 重量级锁中和轻量级锁一样,30bit 的空间用来存放指向重量级锁的指针,2bit 存放锁的标识位,为11
  • GC标记开辟30bit 的内存空间却没有占用,2bit 空间存放锁标志位为11。

其中无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态。

关于为什么这么分配的内存,我们可以从 OpenJDK 中的markOop.hpp类中的枚举窥出端倪

来解释一下

  • age_bits 就是我们说的分代回收的标识,占用4字节
  • lock_bits 是锁的标志位,占用2个字节
  • biased_lock_bits 是是否偏向锁的标识,占用1个字节
  • max_hash_bits 是针对无锁计算的hashcode 占用字节数量,如果是32位虚拟机,就是 32 - 4 - 2 -1 = 25 byte,如果是64 位虚拟机,64 - 4 - 2 - 1 = 57 byte,但是会有 25 字节未使用,所以64位的 hashcode 占用 31 byte
  • hash_bits 是针对 64 位虚拟机来说,如果最大字节数大于 31,则取31,否则取真实的字节数
  • cms_bits 我觉得应该是不是64位虚拟机就占用 0 byte,是64位就占用 1byte
  • epoch_bits 就是 epoch 所占用的字节大小,2字节。

Synchronized锁

synchronized用的锁是存在Java对象头里的。

JVM基于进入和退出 Monitor 对象来实现方法同步和代码块同步。代码块同步是使用 monitorenter 和 monitorexit 指令实现的,monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法结束处和异常处。任何对象都有一个 monitor 与之关联,当且一个 monitor 被持有后,它将处于锁定状态。

根据虚拟机规范的要求,在执行 monitorenter 指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计数器加1,相应地,在执行 monitorexit 指令时会将锁计数器减1,当计数器被减到0时,锁就释放了。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放为止。

Monitor

Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。而操作系统实现线程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么 Synchronized 效率低的原因。因此,这种依赖于操作系统 Mutex Lock 所实现的锁我们称之为重量级锁

Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了偏向锁轻量级锁:锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。

所以锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking=false来禁用偏向锁。

锁的分类及其解释

无锁

无锁状态,无锁即没有对资源进行锁定,所有的线程都可以对同一个资源进行访问,但是只有一个线程能够成功修改资源。

无锁的特点就是在循环内进行修改操作,线程会不断的尝试修改共享资源,直到能够成功修改资源并退出,在此过程中没有出现冲突的发生,这很像我们在之前文章中介绍的 CAS 实现,CAS 的原理和应用就是无锁的实现。无锁无法全面代替有锁,但无锁在某些场合下的性能是非常高的。

偏向锁

Hotspot 的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,还存在锁由同一线程多次获得的情况,偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步时提高性能。

可以从对象头的分配中看到,偏向锁要比无锁多了线程IDepoch,当一个线程访问同步代码块并获取锁时,会在对象头和栈帧的记录中存储线程的ID,等到下一次线程在进入和退出同步代码块时就不需要进行 CAS 操作进行加锁和解锁,只需要简单判断一下对象头的 Mark Word 中是否存储着指向当前线程的线程ID,判断的标志当然是根据锁的标志位来判断的。

偏向锁的获取过程

  1. 访问 Mark Word 中偏向锁的标志是否设置成 1,锁的标志位是否是 01 --- 确认为可偏向状态。
  2. 如果确认为可偏向状态,判断当前线程id 和 对象头中存储的线程 ID 是否一致,如果一致的话,则执行步骤5,如果不一致,进入步骤3
  3. 如果当前线程ID 与对象头中存储的线程ID 不一致的话,则通过 CAS 操作来竞争获取锁。如果竞争成功,则将 Mark Word 中的线程ID 修改为当前线程ID,然后执行步骤5,如果不一致,则执行步骤4
  4. 如果 CAS 获取偏向锁失败,则表示有竞争(CAS 获取偏向锁失败则表明至少有其他线程曾经获取过偏向锁,因为线程不会主动释放偏向锁)。当到达全局安全点(SafePoint)时,会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否存活(因为可能持有偏向锁的线程已经执行完毕,但是该线程并不会主动去释放偏向锁),如果线程不处于活动状态,则将对象头置为无锁状态(标志位为01),然后重新偏向新的线程;如果线程仍然活着,撤销偏向锁后升级到轻量级锁的状态(标志位为00),此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。
  5. 执行同步代码

偏向锁的释放过程

偏向锁的释放过程可以参考上述的步骤4 ,偏向锁在遇到其他线程竞争锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为01)或轻量级锁(标志位为00)的状态。

关闭偏向锁

偏向锁在Java 6 和Java 7 里是默认启用的。由于偏向锁是为了在只有一个线程执行同步块时提高性能,如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

关于 epoch

真正理解 epoch 的概念比较复杂,这里简单理解,就是 epoch 的值可以作为一种检测偏向锁有效性的时间戳

轻量级锁

轻量级锁是指当前锁是偏向锁的时候,被另外的线程所访问,那么偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。

加锁过程

在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为 01 状态,是否为偏向锁为 0 ),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝,然后拷贝对象头中的 Mark Word 复制到锁记录中。

拷贝成功后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针,并将 Lock Record里的 owner 指针指向对象的 Mark Word。

如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为 00 ,表示此对象处于轻量级锁定状态。

如果这个更新操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为 10 ,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。

重量级锁

重量级锁也就是通常说 synchronized 的对象锁,锁标识位为10,其中指针指向的是 monitor 对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。

上图简单描述多线程获取锁的过程,当多个线程同时访问一段同步代码时,首先会进入 Entry Set当线程获取到对象的 monitor 后进入 The Owner 区域并把 monitor 中的 owner 变量设置为当前线程,同时 monitor 中的计数器count 加1,若线程调用 wait() 方法,将释放当前持有的 monitor,owner变量恢复为 null,count自减1,同时该线程进入 WaitSet 集合中等待被唤醒。若当前线程执行完毕也将释放 monitor (锁)并复位变量的值,以便其他线程进入获取monitor(锁)。

由此看来,monitor 对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized 锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时也是 notify/notifyAll/wait 等方法存在于顶级对象Object中的原因。(部分来源于网络)

另外,我自己写了六本 PDF,非常硬核,你可以关注一下我的 头条号 然后私信我 回复 cxuan,我就给你发一下这些宝贵的资源。

相关推荐

史上最全的浏览器兼容性问题和解决方案

微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...

平面设计基础知识_平面设计基础知识实验收获与总结
平面设计基础知识_平面设计基础知识实验收获与总结

CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...

2025-02-21 16:01 yuyutoo

写作排版简单三步就行-工具篇_作文排版模板

和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...

写一个2048的游戏_2048小游戏功能实现

1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...

今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……

  最近的天气暖和得让很多小伙伴们喊“热”!!!  昨天的气温到底升得有多高呢?你家有没有榜上有名?...

CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式

之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...

柠檬科技肖勃飞:大数据风控助力信用社会建设

...

你的自我界限够强大吗?_你的自我界限够强大吗英文

我的结果:A、该设立新的界限...

行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?

行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...

让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华

去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...

2025-02-21 16:00 yuyutoo

今年国家综合性消防救援队伍计划招录消防员15000名

记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...

一起盘点最新 Chrome v133 的5大主流特性 ?

1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...

竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使

style="text-align:center;"data-mce-style="text-align:...

学物理能做什么?_学物理能做什么 卢昌海

作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...

你不知道的关于这只眯眼兔的6个小秘密
你不知道的关于这只眯眼兔的6个小秘密

在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...

2025-02-21 16:00 yuyutoo

取消回复欢迎 发表评论: