谈谈Synchronize锁升级的过程

本文深入探讨了Synchronize锁在多线程环境中的升级过程,包括偏向锁、轻量级锁和重量级锁的转换机制,以及它们在解决并发问题中的作用。

前言

Synchronize作为同步机制中的一大杀器,常常被用于多线程中解决并发问题,在面试中也常作为悲观锁的考察对象【关于乐观锁和悲观锁,感兴趣的话,可以参考我的另一篇博文谈谈个人对乐观锁、悲观锁的理解】。本文不对Synchronize的底层原理进行分析,而是针对面试中常考的一个问题——谈谈Synchronize锁升级的过程表达一下自己的看法,首先介绍一下锁升级涉及到哪些锁。


正文

对象头Header的结构
在这里插入图片描述

1、偏向锁

偏向锁的JDK1.6引入的一项锁优化,为什么要引入它呢?因为经过HotSpot(JDK使用的虚拟机)的作者大量的研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。


偏向锁升级为轻量级锁的过程

当一个线程A进入被修饰的代码块并获取锁对象时,会在对象头中和栈帧中写入当前持有这个偏向锁的ThreadID,偏向锁不会主动释放,当线程A想要再次获取锁时,会比较Java对象头中的ThreadID和当前想要获取这个锁的ThreadID相比较,如果相同,就不用通过CAS来解锁、加锁,而是直接拥有;如果不一致,那么就需要通过Java头中的ThreadID来查看这个Thread是否存活,如果不存活,那么直接将锁对象置为无锁状态,然后当前线程就能拥有偏向锁了;如果存活,那么就需要查看栈帧,判断线程A是否还需要锁,如果不需要,则将锁对象置为无锁状态;如果需要,就将线程A暂停,然后撤销偏向锁,将其设为无锁状态或升级为轻量级锁(标志位置为00)。

2、轻量级锁

轻量级锁是JDK1.6引入的一项锁优化,在介绍轻量级锁之前,先介绍一下自旋锁。


自旋锁

自旋锁在JDK1.4.2中引入。虚拟机的开发团队注意到在许多应用上,共享数据的锁定状态只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一下“,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。

轻量级锁

轻量级锁考虑的场景是锁的竞争不大,而且锁的持有时间较短,因为阻塞线程需要将CPU从用户态转到内核态,这需要较大的开销,如果线程刚进入阻塞状态,又因为锁的释放而被唤醒,那么CPU花费多大代价过大,所以我们考虑不阻塞线程,而让等待的线程自己“忙”一段时间(自旋)。这就是轻量级锁。

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

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

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

如果轻量级锁的更新操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明多个线程竞争锁。


轻量级锁升级为重量级锁的过程

线程自旋需要消耗CPU,因此自旋不能一直进行下去,当自旋达到一定次数时,线程A还没有释放锁,或者线程2在等待过程中,又有第三个线程3来等待获取锁,那么此时轻量级锁就会膨胀为重量级锁,重量级锁会把所有除了拥有锁的线程都阻塞掉,以防止CPU空转。

3、重量级锁

重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。重量级锁的获取过程是通过monitor来实现的。以Synchronize为例。

Synchronized

Synchronized是最常用的线程同步手段之一,90%的线程同步问题都可以使用Synchronized来解决,那么它是如何保证同一时间段内只有一个线程能进入临界区呢?我们通过Synchronized分别修饰代码块和方法进行介绍。首先介绍一下什么是Monitor对象。

  • Monitor对象:在JVM中,对象分为3个部分:Header、Instance Data和Padding。

    • Header: Header包含2部分数据,Mark word(标记字段)、Klass Point(类型指针)

    • Mark Word:默认保存了对象的HashCode、分代年龄和锁标志位信息,可以看到,Mark Word的值会根据锁标志位的变化而变化。

    • Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。在对象头中保存了锁标志位和指向 monitor 对象的起始地址,如下图所示,右侧就是对象对应的 Monitor 对象。
      在这里插入图片描述

当Monitor被某个线程持有后,Owner就会指向这个持有它的线程,_EntryList 用来保存已经获取到锁的线程,_WaitSet用来保存等待获取锁的线程。

修饰方法:使用Synchronized修饰方法时,在方法的字节码上会加上一个标志位ACC_SYNCHRONIZED,当其他线程进入这个方法时,会查看方法是否有这个标志位,如果有,就说明这个锁已经被其他线程占用了,当前线程就不能执行这个方法。

修饰代码块:Synchronized修饰代码块时,是通过monitorenter和monitorexit来实现的,,每个对象都对应着该monitor,当一个monitor被拥有之后就被锁住,其他线程就不能运行到monitorentr指令时,会由于无法获取monitor而陷入阻塞,monitor内部维护这一个计数器,这个计数器记录了当前monitor被拥有的次数,当前拥有它的线程可以重复拥有它,当计数器为0时,表示可以释放当前锁了,于是就执行monitorexit指令,此时其它线程就可以获取锁了。

Java 中,`synchronized` 是一种基于对象头实现的同步机制,用于控制多个线程对共享资源的访问。为了提升性能,JVM 对 `synchronized` 进行了优化,引入了升级机制,包括从**偏向**、**轻量级**到**重量级**的演变过程[^1]。 ### 偏向(Biased Locking) 偏向是为了解决无竞争情况下的获取开销而设计的。当一个线程访问同步块并成功获取后,JVM 会将该对象头中的 Mark Word 设置为偏向状态,并记录持有的线程 ID。后续该线程再次进入同步块时,无需进行 CAS 操作即可直接执行临界区代码,从而减少同步带来的性能损耗[^3]。 例如,以下代码中,第一次调用 `synchronized` 时可能会触发偏向: ```java Object lock = new Object(); new Thread(() -> { synchronized (lock) { // 第一次获取,可能升级为偏向 } }).start(); ``` 一旦有其他线程尝试获取,偏向就会被撤销,并升级为轻量级。 ### 轻量级(Lightweight Locking) 当多个线程同时竞争同一个时,JVM 会尝试使用自旋的方式让线程等待的释放,而不是立即阻塞线程。这种方式称为轻量级。线程会在用户态不断尝试获取,避免了线程上下文切换的开销。如果自旋次数超过阈值或竞争加剧,则会进一步升级为重量级。 轻量级的实现依赖于线程栈帧中的记录(Lock Record)和对象头的 Mark Word 进行 CAS 替换操作。若替换成功则获得,否则进入自旋等待。 ### 重量级(Heavyweight Locking) 当轻量级无法通过自旋获取时,JVM 会将升级为重量级。此时线程会被挂起并进入操作系统内核态的阻塞状态,等待的释放。这种状态涉及线程调度器介入,会产生较大的性能开销,尤其是在频繁发生竞争的场景下[^2]。 重量级的本质是依赖操作系统的互斥量(mutex)来实现线程阻塞与唤醒,因此其性能成本远高于前两种状态。 ### 升级流程总结 - **初始状态**:无。 - **首次访问**:偏向,记录线程 ID。 - **竞争出现**:撤销偏向升级为轻量级,采用自旋机制。 - **自旋失败或竞争激烈**:升级为重量级,线程进入阻塞状态等待唤醒[^1]。 ### 性能影响与优化建议 升级机制旨在平衡性能与并发安全之间的关系。偏向适用于单线程访问场景,轻量级适用于低竞争环境,而重量级则应对高竞争场景。合理使用粒度、减少持有时间、避免不必要的同步操作,有助于降低升级频率,提高系统吞吐量。 ---
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值