1. java对象头
Java 对象头是 JVM 为每个对象在内存中分配的额外数据结构,存储了与对象相关的元信息。它位于对象内存布局的起始部分,是 JVM 管理对象的关键数据结构之一。 对象头主要由三类组成,第一类是Mark Word,它是 Java 对象头的动态部分,用于存储对象的运行时状态信息,根据锁的状态会发生变化。第二类是Class Pointer,它是一个指针,指向该对象的类元数据,JVM 通过它可以确定对象所属的类,包括对象对应的类的类型信息(如方法表、字段表等)。第三类只有对象为数组时才会存在,用来存储数组长度。三类中,我们重点关注第一类Mark Work,是理解java锁的核心。
| 锁状态 | 1bit:是否偏向 | 2bit:锁标志位 | ||
|---|---|---|---|---|
| 无锁 | 25bit:hashcode | 4bit:分代年龄 | 0 | 01 |
| 偏向锁 | 23bit:线程id 2bit:时间戳 | 4bit:分代年龄 | 1 | 01 |
| 轻量级锁 | 29bit:指向栈中锁记录指针 | 无 | 无 | 00 |
| 重量级锁 | 29bit:指向重量级锁的指针 | 无 | 无 | 10 |
| GC标记 | 空 | 空 | 空 | 11 |
2.锁的升级与对比
偏向锁 (Biased Locking)
概念:
偏向锁是一种针对单线程访问场景的优化机制。它假设锁在大多数情况下是由同一线程访问的,因此避免了频繁加锁和解锁的操作。
适用场景:
单线程反复进入同步代码块。
实现原理:
• 锁的所有权会偏向第一个获得它的线程。
• 当这个线程再次进入同步代码块时,不需要进行加锁操作,只需检测 Mark Word 中记录的线程 ID 是否与当前线程匹配。
• 如果匹配,则不需要任何额外操作(类似于无锁)。
优点:
避免了 CAS 操作(Compare-And-Swap),性能较高。
撤销:
如果另一个线程尝试竞争该锁,偏向锁会被撤销,并升级为轻量级锁。
轻量级锁 (Lightweight Lock)
概念:
轻量级锁是一种针对短时间锁竞争的优化机制。通过 CAS 操作(而非阻塞)实现线程之间的锁竞争,避免了重量级锁带来的线程阻塞和上下文切换。
适用场景:
多线程交替短时间访问同步代码块,但线程之间的竞争不激烈。
实现原理:
• 在加锁时,JVM 会尝试将锁对象的 Mark Word 拷贝到当前线程的栈中(称为“锁记录”)。
• 然后通过 CAS 操作尝试将锁对象的 Mark Word 替换为指向锁记录的指针。
• 如果 CAS 成功,说明当前线程获得锁。
• 如果 CAS 失败,则表示有其他线程正在竞争,锁会升级为重量级锁。
优点:
线程不会阻塞,仅使用自旋等待,适用于锁竞争不激烈的场景。
缺点:
自旋需要占用 CPU,若竞争激烈会导致性能下降。
重量级锁 (Heavyweight Lock)
概念:
重量级锁是传统的锁机制,通过操作系统的互斥锁实现,线程阻塞和唤醒都需要操作系统的上下文切换。
适用场景:
多线程激烈竞争同步代码块。
实现原理:
• 当轻量级锁自旋失败时,锁会升级为重量级锁。
• 线程进入阻塞状态,等待其他线程释放锁。
• 使用操作系统的 Mutex(互斥锁)机制实现,阻塞线程会被挂起,等待唤醒。
优点:
避免了自旋造成的 CPU 资源浪费,适合高并发场景。
缺点:
线程阻塞和唤醒需要切换到内核态,性能较低。
3. 总结
锁的状态会随着并发竞争情况升级,且锁只能升级不能降级。
锁的状态流转
-
无锁(对象初始状态)。
-
偏向锁:单线程访问时,进入偏向状态。
-
轻量级锁:多线程访问发生竞争时,偏向锁撤销并升级为轻量级锁。
-
重量级锁:如果轻量级锁竞争过于激烈,升级为重量级锁。
| 锁类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 偏向锁 | 单线程访问 | 减少加锁和解锁的开销 | 需要撤销偏向锁,增加延迟 |
| 轻量级锁 | 竞争不激烈的多线程场景 | 无阻塞,自旋提高性能 | 自旋占用CPU |
| 重量级锁 | 激烈竞争的多线程场景 | 避免自旋浪费CPU | 线程阻塞,切换开销大 |