[[IT知识]] 深入理解Java并发编程:轻量级锁与锁膨胀机制详解

[复制链接]
查看: 39|回复: 0
发表于 2025-1-19 22:39:13 | 显示全部楼层 | 阅读模式
易博V9下载

深入理解Java并发编程:轻量级锁与锁膨胀机制详解

一、轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以 使用轻量级锁来优化。

轻量级锁对使用者是透明的,即语法仍然是

  1. synchronized
复制代码

示例

  1. static final Object obj = new Object();
  2. public static void method1() {
  3. synchronized (obj) {
  4. // 同步块 A
  5. method2();
  6. }
  7. }
  8. public static void method2() {
  9. synchronized (obj) {
  10. // 同步块 B
  11. }
  12. }
复制代码

图解加锁过程

  • 1、创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的
    1. Mark Word
    复制代码
  • 2、让锁记录中
    1. Object reference
    复制代码
    指向锁对象,并尝试用cas替换Object的Mark Word,将Mark Word的值存入锁记录
  • CAS(Compare and Swap):JDK提供的非阻塞原子性操作,它通过硬件保证了比较——更新操作的原子性。
  • 3、如果
    1. cas
    复制代码
    (compare and swap)替换成功,对象头中存储了
    1. 锁记录地址和状态00
    复制代码
    ,表示由该线程给对象加锁
  • 线程中的锁信息和锁对象中的Mark Word进行了互换

  • 4、如果 cas 失败,有两种情况
    • 如果是其它线程已经持有了该 Object 的轻量级锁,这时表明有竞争,进入锁膨胀过程;
    • 如果是自己执行了 synchronized 锁重入,那么再添加一条 Lock Record 作为重入的计数(如下图所示)。

    - 轻量级锁示例代码中t0执行

    1. syn method1(obj)
    复制代码
    ,获得锁之后继续调用
    1. syn method2(obj)
    复制代码
    (多出来一个栈帧,见下图),两个加锁的
    1. obj
    复制代码
    是同一个对象,因此
    1. CAS
    复制代码
    失败

    - 在图中的体现:对象头

    1. lock record 地址 00
    复制代码
    在调用
    1. method1(obj)
    复制代码
    改变了,指向的是第一个栈帧的锁记录,因此第二个栈帧会CAS失败

    -

    1. Lock Record
    复制代码
    的null记录锁重入的计数,如上为1,再调用一次++

  • 5、当退出 synchronized 代码块(解锁时)如果有取值为 null 的锁记录,表示有重入,这时重置锁记录,表示重入计数减一
  • 6、当退出synchronized代码块(解锁时) 锁记录的值不为null,这时使用cas将Mark Word的值恢复给对象头
    • 成功,则解锁成功
    • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程

二、锁膨胀

如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行

  1. 锁膨胀
复制代码
,将轻量级锁变为重量级锁。

示例

  1. static Object obj = new Object();
  2. public static void method1() {
  3. synchronized (obj) {
  4. // 同步块
  5. }
  6. }
复制代码
  • 1、当Thread-1进行轻量级加锁时,Thread-0 已经对该对象加了轻量级锁
  • 2、这时 Thread-1 加轻量级锁失败,进入锁膨胀流程
    • 即为 Object 对象申请 Monitor 锁,让 Object 指向重量级锁地址;
    • 然后自己进入 Monitor 的
      1. EntryList BLOCKED
      复制代码
  • 3、当 Thread-0 退出同步块解锁时,使用 cas 将 Mark Word 的值恢复给对象头,失败。这时会进入
    1. 重量级解锁流程
    复制代码
    • 即按照 Monitor 地址找到 Monitor 对象
    • 设置 Owner 为 null
    • 唤醒
      1. EntryList
      复制代码
      中 BLOCKED 线程

三、自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

自旋定义

是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

自旋重试成功的情况

线程1 (cpu1上) 对象Mark 线程2 (cpu2上)
- 10 (重量锁) -
访问同步块,获取monitor 10 (重量锁) 重量锁指针 -
成功(加锁) 10 (重量锁) 重量锁指针 -
执行同步块 10 (重量锁) 重量锁指针 -
执行同步块 10 (重量锁) 重量锁指针 访问同步块,获取monitor
执行同步块 10 (重量锁) 重量锁指针 自旋重试
执行完毕 10 (重量锁) 重量锁指针 自旋重试
成功(解锁) 无锁 自旋重试
- 10 (重量锁) 重量锁指针 成功(加锁)
- 10 (重量锁) 重量锁指针 执行同步块
- ... ...

*注意:自旋需要cpu资源,所以适合多核cpu*

自旋重试失败的情况

线程1 (cpu1上) 对象Mark 线程2 (cpu2上)
- 10 (重量锁) -
访问同步块,获取monitor 10 (重量锁) 重量锁指针 -
成功(加锁) 10 (重量锁) 重量锁指针 -
执行同步块 10 (重量锁) 重量锁指针 -
执行同步块 10 (重量锁) 重量锁指针 访问同步块,获取monitor
执行同步块 10 (重量锁) 重量锁指针 自旋重试
执行同步块 10 (重量锁) 重量锁指针 自旋重试
执行同步块 10 (重量锁) 重量锁指针 自旋重试
执行同步块 10 (重量锁) 重量锁指针 阻塞
- ... ...

+ 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。

+ 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。

+ Java 7 之后不能控制是否开启自旋功能

易博软件介绍
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1、请认真发帖,禁止回复纯表情,纯数字等无意义的内容!帖子内容不要太简单!
2、提倡文明上网,净化网络环境!抵制低俗不良违法有害信息。
3、如果你对主帖作者的帖子不屑一顾的话,请勿回帖。谢谢合作!
3、问答求助区发帖求助后,如有其他用户热心帮您解决问题后,请自觉点击设为最佳答案按钮。

 
 
QQ在线客服
QQ技术支持
工作时间:
8:00-18:00
软著登字:
1361266号
官方微信扫一扫
weixin

QQ|小黑屋|Archiver|慈众营销 ( 粤ICP备15049986号 )|网站地图

自动发帖软件 | 自动发帖器 | 营销推广软件 | 网络营销工具 | 网络营销软件 | 网站推广工具 | 网络推广软件 | 网络推广工具 | 网页推广软件 | 信息发布软件 | 网站推广工具 | 网页推广软件

Powered by Discuz! X3.4   © 2012-2020 Comsenz Inc.  慈众科技 - Collect from 深圳吉宝泰佛文化有限公司 公司地址:罗湖区黄贝街道深南东路集浩大厦A1403

返回顶部 返回列表