目录
核心思想
Java对象头(Object Header)与Mark Word
锁升级的详细步骤
1. 无锁(No Lock)
2. 偏向锁(Biased Locking)
3. 轻量级锁(Lightweight Lock)
4. 重量级锁(Heavyweight Lock)
流程图
总结与意义
这是一个非常重要的概念,它主要是针对 synchronized
关键字在JVM层面的优化,目的是为了在保证线程安全的同时,尽量减少获取锁和释放锁带来的性能开销。
核心思想
锁升级的核心思想是:JVM会根据实际运行时锁的竞争情况,动态地将锁从低开销的状态升级为高开销、高保障的状态。
在Java早期版本中,synchronized
的实现非常直接,被称为“重量级锁”,性能较差。但从 HotSpot JDK 1.6 开始,JVM团队对 synchronized
进行了重大优化,引入了偏向锁和轻量级锁,以及锁升级的概念,使得它的性能得到了极大的提升,现在在很多场景下已经不再比 ReentrantLock
慢很多。
Java对象头(Object Header)与Mark Word
要理解锁升级,首先要知道Java对象在内存中的布局。每个Java对象在堆内存中都有一个对象头。
对象头主要包含两部分:
-
Mark Word:存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志等。这是锁升级机制的核心。
-
Klass Pointer:指向对象元数据的指针,JVM通过它来确定对象是哪个类的实例。
锁的状态信息就记录在 Mark Word 中。锁升级的过程,其实就是Mark Word中内容变化的过程。
锁升级的详细步骤
锁的升级路径是单向的,从低到高:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。这个过程是不可逆的。
1. 无锁(No Lock)
-
状态:一个新创建的对象,还没有任何线程来竞争它。
-
Mark Word:存储对象的哈希码、分代年龄等信息。锁标志位为
01
。
2. 偏向锁(Biased Locking)
-
设计初衷:在没有实际竞争或只有一个线程多次使用锁的场景下,消除整个同步的开销(比如
StringBuffer
的很多方法都是synchronized
的,但通常只会在单线程中使用)。 -
工作原理:当第一个线程访问同步块时,JVM会将锁置为偏向模式,并在Mark Word中记录这个线程的ID。之后这个线程再次进入和退出同步块时,不需要进行任何CAS操作来加锁和解锁,只需要简单检查一下Mark Word中是否存储着自己的线程ID。
-
升级触发条件:一旦有另一个线程来尝试获取这个锁(发生了竞争),偏向锁就会失效。
-
锁标志位:
01
(同时有额外位标识是否为偏向模式)。
可以把它想象成“贴标签”:第一个线程来了,在对象上贴了个标签“此物归我所有”。以后它再来,看到自己的标签就直接用了。直到有另一个人也想来用,标签就被撕掉。
注意:由于维护偏向锁本身也有开销,且在实际多线程环境中,真正的无竞争场景并不多,从JDK 15开始,偏向锁已被默认禁用。但理解它对于理解整个锁升级体系依然至关重要。
3. 轻量级锁(Lightweight Lock)
-
设计初衷:当锁确实存在竞争,但竞争的激烈程度很低(即线程几乎是交替执行,没有同时抢锁),避免直接使用重量级锁带来的巨大开销。
-
工作原理:
-
当线程要获取锁时,JVM会在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间。
-
将对象头的Mark Word复制到锁记录中(称为Displaced Mark Word)。
-
线程尝试使用CAS操作将对象头的Mark Word替换为指向该锁记录的指针。
-
如果CAS成功,当前线程就获得了锁,此时锁标志位变为
00
。 -
如果CAS失败(说明其他线程已经在竞争这个锁了),当前线程会尝试自旋(循环重试)来获取锁。
-
-
升级触发条件:如果自旋了一定次数后(JVM有自适应策略,自旋次数不固定)还没有获得锁,或者自旋期间又有第三个线程来竞争,轻量级锁就会膨胀为重量级锁。
-
锁标志位:
00
可以把它想象成“抢凳子”:大家(线程)礼貌地转着圈(自旋)等凳子(锁),谁先坐下谁就用。但如果等太久或者人太多,游戏规则就变了。
4. 重量级锁(Heavyweight Lock)
-
设计初衷:处理高激烈度的锁竞争场景。
-
工作原理:此时锁会向操作系统内核申请互斥量(Mutex)。未能获取到锁的线程会被挂起(进入阻塞状态),并放入一个等待队列中。当锁被释放时,操作系统会负责唤醒等待队列中的线程,让它们重新竞争锁。
-
开销:这个过程中涉及用户态到内核态的切换、线程的挂起和唤醒,是开销最大的一种锁状态。
-
锁标志位:
10
可以把它想象成“正式排队”:没拿到锁的人不再自己傻等,而是去休息室(等待队列)睡觉(阻塞),由管理员(操作系统)叫号唤醒。
流程图
下图清晰地展示了锁升级的全过程:
总结与意义
-
优化目的:锁升级是一种“按需付费”的优化机制。JVM会根据竞争的激烈程度,从低开销的方案开始尝试,只有在不得已时才会使用开销最大的方案。这极大地提高了
synchronized
在常见场景下的性能。 -
不可逆性:锁只能升级,不能降级。这是为了节省在激烈竞争环境下不必要的降级开销。
-
实际应用:对于开发者来说,这个过程是完全透明的,由JVM自动完成。我们只需要安心使用
synchronized
关键字,JVM会在背后为我们选择最优的锁方案。 -
与ReentrantLock的对比:
ReentrantLock
的实现更类似于“一开始就是重量级锁”的思路(虽然它也在用户态做了很多优化,如CAS自旋),但它提供了更灵活的功能(如可中断、公平锁等)。而synchronized
的优势在于语法简洁和JVM的自动优化。