JAVA:简单易懂了解锁升级
为什么有锁升级:性能优化的智慧
锁升级的核心目标是在保证线程安全的前提下,最大限度地减少锁带来的性能开销。
举个栗子:
用一个简单易懂的“办公室会议室”比喻,给你讲清楚Java的锁升级。
首先明确一点:锁升级是JVM为了优化 synchronized关键字性能而设计的内部机制。它的核心思想是:大多数情况下,锁不仅不存在竞争,而且总是由同一个线程多次获得。 因此,没必要一开始就用“大炮”(重量级锁),而是从“小装备”开始,根据需要逐步升级。
整个过程分为4个状态,我们想象一个“锁对象”(比如一个会议室)的生命周期:
第一步:无锁状态
- 场景:会议室(锁对象)刚建好,还没人用。任何线程(员工)都可以直接进去使用。
- 对应:一个新创建的对象,还没被任何线程加锁。
第二步:偏向锁 (单线程多次访问)
- 核心思想:这个锁“偏爱”第一个来用它的人。 假设员工A第一个使用了会议室。
- JVM操作:JVM会在这个会议室门口贴一张“常驻牌”,上面写着“A专属”。同时,在对象头里记录A的线程ID。
- 后续:
- 如果A下次又来用会议室,一看牌子是自己的,直接进去,没有任何额外检查,开销极低。
- 如果员工B也想用,会发现牌子是A的(发生了竞争)。JVM会先暂停A,检查A是否还在用会议室。
- 如果A已经用完了(同步代码块已退出),JVM就把牌子撕掉,然后重新贴成B的(偏向锁撤销并重偏向)。
- 如果A还在用,说明有竞争,偏向锁就需要升级。
- 目的:消除同一线程重入锁的开销。适用于只有一个线程反复进入同步块的场景。
第三步:轻量级锁 / 自旋锁 (少量线程短时间竞争)
- 场景:当偏向锁因为竞争(B也想用)而升级后,就进入这个状态。
- 比喻:现在会议室没有“常驻牌”了。A在里面开会,B来了。B不会去“领导办公室”(操作系统内核)那里投诉,而是选择在门口等一会儿(自旋),边等边嘀咕:“A应该快出来了吧?我再等等看……”
- JVM操作:
- JVM会在当前线程的栈帧中创建一个“锁记录空间”,并拷贝会议室门上的标记。
- 然后尝试用CAS操作(一种乐观的原子操作)把会议室门牌换成指向自己“锁记录”的指针。如果成功,就拿到锁。
- 竞争线程(B)通过自旋(循环尝试CAS)来尝试获取锁。
- 优点:避免了直接惊动操作系统内核(重量级操作),在竞争不激烈、等待时间极短的情况下效率很高。
- 缺点:如果B在门口等得太久(自旋超过一定次数或自旋线程数增加),就会白占着CPU资源,属于“空转”,得不偿失。
第四步:重量级锁 (真正激烈的多线程竞争)
- 场景:如果B在门口自旋等了很久(比如自旋了10次),A还没出来,或者又有C、D、E…一群员工都在门口自旋等着。
- JVM操作:这时候,JVM就会判定竞争太激烈了。它会将轻量级锁升级为重量级锁。
- 比喻:
- B不再在门口傻等,而是去“领导办公室”(操作系统内核)正式登记排队。
- 操作系统会把B放到一个等待队列里,并挂起B的线程(让B去休息,不占用CPU)。
- 当A用完会议室出来时,会去“领导办公室”报告。操作系统会从等待队列里叫醒一个(比如B),让它去用会议室。
- 特点:
- 开销大:涉及到操作系统内核的线程调度、状态切换(用户态 -> 内核态)。
- 不占用CPU:没拿到锁的线程会被挂起,不消耗CPU周期。
- 适合场景:高并发、长耗时的同步场景。
流程图

核心要点:
- 升级是单向的: 从无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。降级理论上会发生但条件苛刻,可以忽略。
- 目的: 在没有竞争或竞争不激烈的情况下,用最小的开销实现同步。
- 自动化: 整个过程由JVM自动完成,程序员只需正确地使用
synchronized。
简单记忆:
- 偏向锁: “这是我专用。”(适合单线程)
- 轻量级锁: “我在门口等会儿。”(适合短时间、低竞争)
- 重量级锁: “我去领导那排队。”(适合高并发、长耗时)