java synchronized中锁的升级过程

本文详细介绍了Java对象头的结构和锁的升级过程,包括偏向锁、轻量级锁、重量级锁和自旋锁的概念。在没有锁竞争时,偏向锁使得获取锁更为高效;轻量级锁利用自旋避免线程切换,降低开销。当锁竞争激烈时,锁会升级为重量级锁,涉及线程阻塞和唤醒,增加了开销。线程切换是重量级锁‘重’的主要原因,而轻量级锁则通过自旋避免了这种切换。

synchronized 偏向锁、轻量级锁、重量级锁、自旋锁

对象头

java对象在jvm内存中分为对象头、对象体两部分,对象头中有些标记信息,称为标记字(mark word),mark word主要有对象hash值、gc年龄、锁标记、线程id

synchronized中锁的升级过程

对象刚创建时,没有锁,第一个线程来时,使用CAS(Compare And Swap)将当前线程id设置到对象头(Mark Word)中,第二次再获取锁时,不需要进行CAS,只比较一下对象头中的线程id是否与当前线程id相同,如果相同则获取锁,这就叫偏向锁;如果不相等,则检查持有锁线程的状态,如果持有锁线程已经消亡或者已经退出同步块,则撤销锁,重新偏向;如果原有线程还需要锁,则升级为轻量级锁,首先在持有锁的线程栈中创建锁记录,将对象头中的mark word复制到锁记录,对象头有指针指向锁记录,当前线程进入自旋,如果自旋超过一定次数或者有第三个线程参与锁竞争,则认为锁竞争激烈,锁膨胀为重量级锁;

  • 偏向锁
    • 是假设没有线程竞争,偏向第一个获取锁的线程,对象会偏向上次获取到锁的线程。
  • 自旋
    • 通常物理处理器都是多核,当线程1持有锁时,线程2尝试获取锁,通常我们认为线程同步块应该会很快结束,为了避免线程切换引起的性能开销,让线程2自旋(占用另外一个cpu空转,可以认为是while循环),这就是自旋;轻量级锁搭配自旋锁使用。
  • 轻量级锁
    • 锁竞争不激烈,持有锁线程正常执行,当前线程自旋;
  • 重量级锁
    • 适应锁竞争激烈情景,持有锁的线程正常执行,当前线程会阻塞挂起。

显而易见,偏向锁、轻量级锁是乐观锁;重量级锁是悲观锁;

引用知乎的一张图,图里锁升级的过程很清晰。
在这里插入图片描述

问题

轻量级锁轻量在哪里?重量级锁重量在哪里?
这个问题曾困扰了我很长时间,《深入理解 Java 虚拟机–JVM高级特性与最佳实践》说重量级锁通过互斥量实现,但我对互斥量不懂,其实互斥量只是手段,最终引起线程的切换,重就重在线程切换
轻量级锁的轻量是相当于重量级锁而言的,那什么是重量级锁,重量级锁使用操作系统互斥量实现的,如果线程1持有重量级锁,线程2也想获得这把锁时,因为已经被持有,线程2会被挂起阻塞,这个线程1释放锁后,线程2再被唤醒,这个过程就涉及到线程的阻塞、唤醒,阻塞唤醒牵扯到内核态、用户态的切换,线程切换的成本是很大的,所以叫重量级锁;而轻量级锁不牵扯到线程切换,当线程2获取锁时,看到线程1已经持有锁,那么线程2会自旋(使用另外一个物理cpu执行while死循环),这个过程没有线程切换,所以是轻量级。

<think>我们正在讨论Java中的synchronized关键字及其升级机制。根据引用内容,升级机制是为了在不同并发场景下提高性能而设计的。升级过程是不可逆的,从偏向开始,到轻量级,最后到重量级。下面我将详细解释每个阶段。 首先,需要了解Java对象头中的Mark Word,它存储了对象的哈希码、分代年龄和标志位等信息。的状态就记录在Mark Word中。 1. **偏向阶段**: 当第一个线程访问同步块时,会在对象头和栈帧中记录偏向的线程ID。以后该线程进入和退出同步块时不需要进行CAS操作来加和解,只需检查Mark Word中是否存储了当前线程ID。如果存在,表示线程获得了。这适用于只有一个线程访问同步块的场景[^1]。 2. **轻量级阶段**: 当有第二个线程尝试获取时,偏向就会升级为轻量级。轻量级通过CAS操作来尝试获取。如果成功,则使用轻量级;如果失败,表示存在竞争,线程会通过自旋(循环尝试)来等待的释放。自旋避免了线程阻塞和唤醒的开销,适用于占用时间短的场景[^2]。 3. **重量级阶段**: 如果自旋一定次数后(或者自旋过程中有第三个线程来竞争),轻量级升级为重量级。此时,未获得的线程会被阻塞,不再消耗CPU资源。重量级依赖于操作系统的互斥量(mutex),需要进行用户态到内核态的切换,开销较大[^3]。 升级的整个过程是为了在低竞争时减少开销,而在高竞争时避免过度消耗CPU资源。 §§相关问题§§ 1. 偏向的撤销过程是怎样的? 2. 为什么升级过程是不可逆的? 3. 在什么情况下会直接使用重量级?</think>### Java synchronized 升级机制详解 Java的`synchronized`升级机制是JDK1.6引入的性能优化方案,通过**无→偏向→轻量级→重量级**的阶梯式升级,在不同并发场景下动态选择最优策略[^1][^2]。整个过程不可逆,具体流程如下: #### 1. **偏向阶段** 当第一个线程访问同步块时: ```java synchronized(obj) { // 首次访问触发偏向 // 临界区代码 } ``` - 在对象头的Mark Word中记录**线程ID**和**偏向时间戳** - 后续该线程进入时只需检查线程ID是否匹配,无需CAS操作 - **适用场景**:单线程重复访问同步块(如循环内同步)[^1] #### 2. **轻量级阶段** 当第二个线程尝试获取时: ```java // 线程1持有时,线程2尝试获取 synchronized(obj) { // 触发升级 } ``` - JVM在**当前线程栈帧**创建Lock Record空间 - 通过**CAS自旋**尝试将对象头指向Lock Record - **成功**:获取轻量级(约5-10次自旋) - **失败**:升级为重量级 - **优势**:避免用户态→内核态切换(约节省1μs)[^2] #### 3. **重量级阶段** 当竞争激烈时(如自旋失败或第三个线程加入): - 向操作系统申请**mutex互斥** - 未获的线程进入**阻塞队列**(WAITING状态) - 触发线程上下文切换(用户态→内核态) - **开销**:约10μs级操作,但避免CPU空转[^3] #### 升级示意图 ``` 无 → (首次访问) → 偏向 → (竞争发生) → 轻量级 → (自旋失败/多线程竞争) → 重量级 ``` #### 关键特性 1. **不可逆性**:升级后不会降级(避免频繁状态切换开销) 2. **自适应自旋**:JVM根据历史成功率动态调整自旋次数 3. **批量重偏向**:当一类对象被多个线程交替访问时,JVM会批量重置偏向 > 实验数据:在单线程场景下,偏向比轻量级快约5倍;低竞争时轻量级比重量级快20倍以上[^1][^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值