Java synchronized锁升级机制与性能优化实践
在Java虚拟机规范中,synchronized关键字通过监视器锁(Monitor)实现线程同步。但鲜为人知的是,这个看似简单的同步机制背后隐藏着精妙的锁状态转换体系。通过对象头的巧妙设计和运行时系统的动态优化,JVM实现了从无锁状态到偏向锁、轻量级锁,最终到重量级锁的三级升级体系。这种自适应机制使得同步操作在保证线程安全的前提下,能够根据实际竞争情况动态调整同步策略,将性能损耗降到最低。
对象头与锁状态存储
每个Java对象在堆内存中的存储布局都包含对象头(Header),这是实现锁机制的关键数据结构。在64位JVM中,对象头结构如下:
|------------------------------------------------------------------|
| Mark Word (64 bits) | State |
|------------------------------------------------------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | 01 | Normal |
|------------------------------------------------------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01 | Biased |
|------------------------------------------------------------------|
| ptr_to_lock_record:62 | 00 | Lightweight |
|------------------------------------------------------------------|
| ptr_to_heavyweight_monitor:62 | 10 | Heavyweight |
|------------------------------------------------------------------|
| | 11 | Marked |
|------------------------------------------------------------------|
通过OpenJDK提供的JOL工具,可以直观查看对象头信息变化:
public class ObjectHeaderExample {
public static void main(String[] args) {
Object obj = new Object();
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
}
}
}
输出结果展示锁状态变化:
# 无锁状态
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) 01 00 00 00
4 4 (loss due to the next object alignment)
# 轻量级锁状态
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION
0 4 (object header) f0 f8 3f 03
4 4 (loss due to the next object alignment)
偏向锁(Biased Locking)
当单个线程反复获取同一个锁时,偏向锁通过消除同步操作的开销实现优化。对象头中存储持有锁的线程ID和epoch值,实现机制如下:
- 首次获取锁时,CAS操作将线程ID写入对象头
- 后续进入同步块时直接检查线程ID是否匹配
- 出现竞争时撤销偏向锁状态
测试代码示例:
public class BiasedLockTest {
static final int LOOP = 1000000;
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
// 触发偏向锁延迟禁用(默认约4秒)
Thread.sleep(5000);
long start = System.currentTimeMillis();
for (int i = 0; i < LOOP; i++) {
synchronized (lock) {
// 空同步块测试性能
}
}
System.out.println("Biased lock cost: " + (System.currentTimeMillis() - start));
}
}
典型输出:
Biased lock cost: 12 ms
轻量级锁(Lightweight Locking)
当多个线程交替访问同步块时,JVM将锁升级为轻量级锁。核心机制是通过线程栈中的Lock Record实现CAS自旋:
- 在栈帧中创建Lock Record,拷贝对象头中的Mark Word
- 使用CAS尝试将对象头指针指向Lock Record
- 成功则获取锁,失败则升级为重量级锁
自旋锁实现示例:
public class LightweightLockTest {
static int counter = 0;
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
synchronized (lock) {
counter++;
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
synchronized (lock) {
counter--;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final counter: " + counter);
}
}
重量级锁(Heavyweight Locking)
当发生激烈锁竞争时,JVM通过操作系统的互斥量(mutex)实现同步。此时对象头指向Monitor对象,包含:
- _owner:持有锁的线程
- _EntryList:等待锁的线程队列
- _WaitSet:调用wait()的线程集合
性能对比测试:
public class HeavyweightLockBenchmark {
static final int THREADS = 50;
static final int ITERATIONS = 10000;
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(THREADS);
long start = System.currentTimeMillis();
for (int i = 0; i < THREADS; i++) {
executor.submit(() -> {
for (int j = 0; j < ITERATIONS; j++) {
synchronized (lock) {
// 模拟业务操作
Math.sqrt(new Random().nextDouble());
}
}
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.HOURS);
System.out.println("Total time: " + (System.currentTimeMillis() - start));
}
}
锁升级全流程解析
完整的状态转换路径如下:
- 初始状态:无锁(001)
- 首次访问:偏向锁(101)
- 出现竞争:撤销偏向锁转为轻量级锁(00)
- 自旋失败:膨胀为重量级锁(10)
- 锁释放:根据情况可能降级
转换条件示例:
public class LockUpgradeDemo {
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
// 阶段1:偏向锁
synchronized (lock) {
printHeader("After first lock");
}
// 阶段2:轻量级锁
new Thread(() -> {
synchronized (lock) {
printHeader("Second thread locked");
}
}).start();
// 阶段3:重量级锁
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
synchronized (lock) {
printHeader("Contended lock");
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
});
}
executor.shutdown();
}
static void printHeader(String msg) {
System.out.println(msg + ": " + ClassLayout.parseInstance(lock).toPrintable());
}
}
锁优化策略与参数调优
JVM提供多个参数控制锁行为:
-XX:+UseBiasedLocking
:启用偏向锁(Java 15后默认禁用)-XX:BiasedLockingStartupDelay=0
:禁用偏向锁延迟-XX:+UseSpinning
:启用自旋优化-XX:PreBlockSpin=10
:设置自旋次数阈值
性能调优建议:
- 减少同步块粒度
- 避免在循环内同步
- 使用
java.util.concurrent
工具类替代 - 分析竞争模式选择合适锁类型
现代JVM的锁改进
JDK16引入的弹性元空间(Elastic Metaspace)优化了Monitor分配,ZGC收集器减少了STW对锁操作的影响。Project Loom的虚拟线程对传统锁机制提出新挑战,未来可能引入更细粒度的锁优化策略。
正文到此结束
相关文章
热门推荐
评论插件初始化中...