Java中的锁升级

Java中锁升级的过程

首先先看一下《Java并发编程的艺术》中的一段话,也是对整个锁升级过程的一个总结和描述。

Java SE 1.6为了减少获得锁和释放锁带来的性能消耗,引入了“偏向锁”和“轻量级锁”,在Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。
——引用自《Java并发编程的艺术》

对象头

java对象头是实现synchronized的锁对象的基础,synchronized使用的锁对象是存储在Java对象头里的。
如果对象是数组类型,则虚拟机用3个字宽(Word)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,1字宽等于4字节,即32bit,如下表所示:

长度内容说明
32/64bitMarkWord存储对象的hashCode或锁信息等
32/64bitClass Metadata Address类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例
32/64bitArray length数组的长度(如果当前对象是数组)

Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。
32位JVM的Mark Word的默认存储结构如下表所示:

锁状态25bit4bit1bit是否是偏向锁2bit锁标志位
无所状态对象的hashCode对象的分代年龄001

在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。Mark Word可能变化为存储以下4种数据,如图所示:
Mark Word的状态变化

偏向锁

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入偏向锁。
当一个线程访问同步代码块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,
以后该线程再进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需要简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。
如果测试成功,表示线程已经获得了锁。
如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置为1(表示指向当前进程):
如果没有,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前进程。
——《Java并发编程的艺术》

JVM使用CAS操作把线程ID记录到了这个Mark Word当中,修改了标识位,当前线程就拥有这把锁了
偏向锁状态下的对象头
可以看出:JVM不用和操作系统协商设置Mutex,它只记录下线程ID,就表示当前线程拥有这把锁了,不用操作系统介入。

这时线程获得了锁,可以执行synchronized修饰的代码块。
当线程再次执行到这个synchronized的时候,JVM通过锁对象的Mark Word判断:“当前线程ID还在,还持有着这个对象的锁,就可以继续进入临界区执行

这就是偏向锁,在没有别的线程竞争的时候,一直偏向当前线程,当前线程可以一直执行

轻量级锁(自旋锁)

轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁
轻量级锁的加锁过程:

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

  2. 拷贝对象头中的Mark Word复制到锁记录中;

  3. 拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤4,否则执行步骤5。

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

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

重量级锁

轻量级锁膨胀之后,就升级为重量级锁了。
重量级锁是依赖对象内部的monitor锁来实现的,而monitor又依赖操作系统的MutexLock(互斥锁)来实现的,所以重量级锁也被成为互斥锁。

synchronized就是一个典型的重量级锁 synchronized关键字

总结

锁状态的优缺点对比:
锁的优缺点对比

参考

https://blog.csdn.net/sspudding/article/details/89563462

<think>嗯,用户想了解Java中的升级机制,我需要先回忆一下相关知识。升级主要是为了优化多线程环境下的性能,减少竞争。记得Java的synchronized关键字背后有的状态变化,比如偏向、轻量级、重量级这几个阶段。首先,对象头里的Mark Word存储了的状态信息,这是关键。 用户提供的引用里提到,升级发生在使用synchronized或者JVM的管理时。偏向是为了在无竞争情况下减少同步开销,当一个线程访问同步块时,会在Mark Word里记录线程ID。这时候如果有其他线程来竞争,就会升级到轻量级,也就是自旋。轻量级通过CAS操作来尝试获取,避免线程阻塞,但如果自旋次数太多或者等待线程过多,就会进一步升级为重量级,这时候会用到操作系统的互斥量,导致线程挂起,增加开销。 需要解释清楚每个的阶段,以及升级的条件。比如偏向适用于单线程重复进入的情况,轻量级适用于低竞争,而重量级应对高并发。还要提到JDK的版本变化,比如1.6之后引入了自适应的自旋,JVM会自动调整参数,不需要手动调优。另外,引用4中提到JDK1.6的规定,自旋次数和线程数超过CPU核数一半时会升级,但现在由JVM自适应管理,这点需要强调。 可能用户还关心升级带来的性能影响,比如在什么情况下升级反而会降低效率,或者如何避免不必要的升级。不过用户的问题主要是机制和原理,所以重点放在流程和实现上。需要结合对象头结构、CAS操作、操作系统原语等方面,把整个过程串起来,确保解释清晰。 另外,用户提供的引用中有几个关键点:升级的触发条件,Mark Word的作用,Monitor机制与操作系统原语的关系。这些都需要整合到回答中,并且正确引用对应的注释标号。比如引用1提到升级是为了减少竞争,引用3说明信息存放在Mark Word,引用4详细说明了升级为重量级的条件。 最后,生成的相关问题需要围绕升级的细节,比如各阶段区别、性能影响、如何监控等,帮助用户进一步理解。确保回答结构清晰,分步骤解释升级的流程,每个阶段的特点和转换条件,同时保持语言通俗易懂,避免过于技术化的术语堆积。</think>### Java升级机制原理与实现分步说明 #### 一、升级的背景 Java升级机制旨在平衡线程安全与性能,通过逐步提升粒度来应对不同竞争强度场景。在JDK 1.6后引入的优化方案中,状态分为三个阶段: $$无 \rightarrow 偏向 \rightarrow 轻量级 \rightarrow 重量级$$ --- #### 二、核心组件:对象头与Mark Word 1. **对象头结构** 每个Java对象头部包含`Mark Word`字段,存储状态、哈希码、GC年龄等信息[^3]。 例如32位JVM的Mark Word结构: ``` | 状态 | 25位存储内容 | 4位标志位 | |------------|----------------------|------------| | 无 | 哈希码+分代年龄 | 001 | | 偏向 | 线程ID+时间戳 | 101 | | 轻量级 | 指向栈记录的指针 | 00 | | 重量级 | 指向Monitor的指针 | 10 | ``` --- #### 三、升级流程 1. **偏向(Biased Locking)** - **触发条件**:首次线程访问同步块时无竞争 - **实现**:通过CAS将线程ID写入Mark Word - **优势**:消除无竞争情况下的同步开销 - **失效条件**:第二个线程尝试获取时触发撤销 2. **轻量级(Lightweight Locking)** - **触发条件**:出现低强度线程竞争 - **实现**: 1. 在栈帧创建`Lock Record`空间 2. 通过CAS将Mark Word复制到Lock Record 3. 尝试将Mark Word更新为指向Lock Record的指针[^3] - **优势**:通过自旋避免线程阻塞 - **升级条件**:自旋超过阈值(默认自适应)或竞争加剧[^4] 3. **重量级(Heavyweight Locking)** - **触发条件**:高强度竞争或自旋失败 - **实现**:通过操作系统`monitor`机制(mutex lock)管理线程阻塞/唤醒[^2] - **特点**:涉及内核态切换,性能开销最大 --- #### 四、关键参数与自适应优化 1. **自旋阈值**:JDK 1.6后采用**自适应自旋**,JVM根据前次自旋成功率动态调整等待时间[^4] 2. **批量重偏向**:当同一类产生20个偏向撤销时,JVM会重新允许后续偏向新线程 3. **批量撤销**:当偏向撤销次数达到40次,该类的所有实例偏向将被禁用 --- #### 五、性能对比 | 状态 | 响应速度 | 适用场景 | 资源消耗 | |----------|----------|------------------------|----------| | 偏向 | 最快 | 单线程重复访问 | 最低 | | 轻量级 | 较快 | 低并发短时竞争 | 中等 | | 重量级 | 最慢 | 高并发或长时间线程阻塞 | 最高 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值