Java 加锁与解锁

哒哒队长

今天看极客时间的时候看到了 java 锁的一篇文章,其中关于加锁与解锁的几个点不是特别理解,晚上专门差了一下资料,算是弄明白了。

疑问

为什么解锁的时候,cas 释放失败就证明有其他线程来获取过锁呢?

原理

每一个java对象都拥有一个对象头,对象头分为2个部分:Mark Word(标记字段) 与 Class Pointer(类型指针)。

  • Mark Word用于存储对象自身的运行时数据,自身运行时的数据:哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,它是实现轻量级锁和偏向锁的关键。
  • Class Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

32位虚拟机的Mark Word如下:
Mark Word可能存储4种数据

分析

先说一下锁的种类:偏向锁,轻量级锁,重量级锁。


偏向锁

获取流程:

  1. 确认对象是可偏向状态(是否偏向:1,锁标致:01);
  2. 可偏向,查询 Mark Word 中 线程ID是否为当前线程ID,是则进行5,不是则3;
  3. 未指向当前线程,则 CAS 尝试获取锁,获取成功,将 Mark Word 中线程ID更换,执行5,否则执行4;
  4. 如果获取失败,则说明有线程竞争,那么在 safe-point 安全点时,获取锁的线程挂起,升级为轻量级锁,然后被阻塞在安全点的线程继续执行后面的代码;
  5. 执行代码。

释放流程:

不主动释放,只有当出现竞争,进入到安全点时才会触发释放逻辑,这也是3为什么会获取成功的原因。
结果是要么释放被其他线程获取,要么升级轻量级锁。


轻量级锁

获取流程:

  1. 线程进入同步代码块时,如果是偏向锁的状态;线程在自己的栈帧空间内建立一个 Mark Record 的空间,用于存储 Mark Word 的备份,官方称为 Displaced Marl Word,这是线程与对象头的状态是这样的:
  2. 拷贝 Mark Word 到 Lock Record 中;
  3. CAS 尝试将对象头的 Mark Word 指向 Lock Record,并将 Lock Record 中的 owner 执行 mark word,如果成功执行4,否则5;
  4. 如果更新成功,就证明得到锁,并将锁标致位更新为00,表示对象处于轻量级锁,此时线程与对象头的状态:
  5. 如果更新失败,jvm 先判断 mark word 是否指向当前栈帧,如果是代表线程已经获取锁,进入重入状态;如果不是证明已经被其他线程获取,自旋等待一定时间,如果仍未获取,升级为重量级锁(将 mark work 从指向线程指针改为指向互斥量)。

释放流程

  1. 通过 CAS 将线程中复制的 Displaced Mark Work 替换会原先的 Mark Word;
  2. 替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。

这里说一下轻量级锁释放失败是就证明锁升级的原理,因为之前 mark word 指向的是本线程的指针,这个是 cas 期望的值,但是被其他线程更改为了指向互斥量的对象了,cas 就失败,就证明升级为了重量级锁。

参考文档

https://www.itqiankun.com/art...
https://juejin.im/post/5b4eec...

阅读 1.5k

7 声望
0 粉丝
0 条评论
推荐阅读
Liquibase 数据库版本管理工具:3. changeSet 变更集详解
上篇文章中详细了介绍了一下changelog 文件的使用,本篇文章将详细说一下 changeSet 变更集 中的细节,以及通常的使用方式 1.变更集分类 changeSet 分为 6类: add create drop rename sql other 官方文档:[链接...

哒哒队长1阅读 3.8k评论 1

【已结束】SegmentFault 思否技术征文丨浅谈 Java 进阶之路
本期中奖名单:[链接]亲爱的开发者们:大家好,我是 SegmentFault 思否的社区管理员思否小姐姐 ~ 我们的社区征文活动再次起航,这一次,想邀请作者们分享原创技术内容。将于 9-12 月会相继上线技术专题征文活动,...

SegmentFault思否9阅读 4.4k评论 20

封面图
10分钟教你写一个数据库
今天教大家借助一款框架快速实现一个数据库,这个框架就是Calcite,下面会带大家通过两个例子快速教会大家怎么实现,一个是可以通过 SQL 语句的方式可以直接查询文件内容,第二个是模拟 Mysql 查询功能,以及最后...

艾小仙8阅读 2.1k评论 1

从前端到全栈 -- 最全面向对象总结
从事前端的这两年中,也接触了后端领域,像 PHP Node, 并且都用它们做过一些 demo 和 私活 ,站在职业发展道路上来讲,了解后端是好的,自己可以熟练前后的开发流程,整条链路下来,很清晰,懂了后端,在自己以后...

程序员海军8阅读 2.1k

封面图
性能优化必备——火焰图
本文主要介绍火焰图及使用技巧,学习如何使用火焰图快速定位软件的性能卡点。结合最佳实践实战案例,帮助读者加深刻的理解火焰图构造及原理,理解 CPU 耗时,定位性能瓶颈。

咕咕鸡7阅读 1.9k

封面图
我终于会写 Java 的定时任务了!
前言学过定时任务,但是我忘了,忘得一干二净,害怕,一直听别人说:你写一个定时任务就好了。写个定时任务让他去爬取就行了。我不会,所以现在得补回来了,欠下的终究要还的,/(ㄒoㄒ)/~~定时任务是什么?大家都...

god23bin8阅读 1.4k

封面图
记录一个关于 GBK 编码的问题
UTF-8 是一种国际化的编码方式,包含了世界上大部分的语种文字(简体中文字、繁体中文字、英文、日文、韩文等语言),也兼容 ASCII 码。

编程码农5阅读 1.2k

7 声望
3 粉丝
宣传栏