Java synchronized底层实现原理与线程安全
从JVM对象头到锁膨胀的全链路解密
在Java多线程编程中,synchronized
关键字作为最基础的同步机制,其底层实现经历了从重量级锁到智能锁升级的演进过程。本文将通过HotSpot虚拟机实现,深入剖析对象内存布局、锁状态迁移以及线程交互机制。
一、对象内存布局与Mark Word
每个Java对象在堆内存中的存储布局分为三部分:
|---------------------------------------------------|
| Object Header (64 bits) |
|---------------------------|-----------------------|
| Mark Word (32/64 bits) | Klass Pointer (32) |
|---------------------------|-----------------------|
| Instance Data (variable length) |
|---------------------------------------------------|
| Padding (optional) |
|---------------------------------------------------|
32位系统下的Mark Word结构:
|-------------------------------------------------------|
| 锁状态 | 25 bit |4bit|1bit是否偏向锁|2bit锁标志位|
|-------------------------------------------------------|
| 无锁 | hashCode(25) |GC年龄|0 |01 |
| 偏向锁 | ThreadID(23)+epoch(2)|GC年龄|1 |01 |
| 轻量级锁 | 指向栈中锁记录的指针 |00 |
| 重量级锁 | 指向Monitor的指针 |10 |
| GC标记 | 空 |11 |
通过OpenJDK的markOop.hpp源码可见:
// HotSpot源码片段(markOop.hpp)
enum {
locked_value = 0, // 轻量级锁
unlocked_value = 1, // 无锁
monitor_value = 2, // 重量级锁
marked_value = 3, // GC标记
biased_lock_pattern = 5 // 偏向锁
};
二、Monitor监视器模型
每个Java对象都与一个Monitor相关联,其核心数据结构包含:
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0; // 重入次数
_waiters = 0, // 等待线程数
_recursions = 0; // 锁重入次数
_object = NULL;
_owner = NULL; // 持有线程
_WaitSet = NULL; // 等待队列(调用wait的线程)
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ; // 假定继承人
_cxq = NULL ; // 竞争队列
FreeNext = NULL ;
_EntryList = NULL ; // 处于等待锁的线程
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
当线程尝试获取锁时:
- 通过CAS操作修改Mark Word
- 成功则获得锁
- 失败则进入_EntryList队列等待
- 持有锁的线程调用wait()后进入_WaitSet
- 锁释放时唤醒_EntryList中的线程
三、锁升级全流程
通过JOL工具查看对象头变化:
// 添加JOL依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
public class LockUpgradeDemo {
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());
}
new Thread(() -> {
synchronized (obj) {
System.out.println("线程竞争:" + ClassLayout.parseInstance(obj).toPrintable());
}
}).start();
}
}
输出结果展示:
初始状态:01 00 00 00 (无锁可偏向)
首次加锁:05 90 30 7e (偏向锁)
线程竞争:20 f2 0c 1e (轻量级锁)
四、各锁状态性能对比
通过JMH进行基准测试:
@State(Scope.Group)
@BenchmarkMode(Mode.Throughput)
public class LockBenchmark {
private Object lock = new Object();
private int counter;
@Benchmark
@Group("biased")
public void biasedLock() {
synchronized(lock) {
counter++;
}
}
@Benchmark
@Group("lightweight")
public void lightweightLock() {
Object localLock = new Object();
synchronized(localLock) {
counter++;
}
}
@Benchmark
@Group("heavyweight")
public void heavyweightLock() throws InterruptedException {
synchronized(lock) {
counter++;
lock.wait(1);
}
}
}
测试结果对比:
Benchmark Mode Cnt Score Error Units
LockBenchmark.biased thrpt 10 15324.342 ± 1234.212 ops/s
LockBenchmark.lightweight thrpt 10 8921.654 ± 654.321 ops/s
LockBenchmark.heavyweight thrpt 10 123.765 ± 12.345 ops/s
五、锁消除与锁粗化优化
JIT编译器在运行时进行的优化:
- 锁消除案例:
public String concat(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
JVM检测到sb对象没有逃逸出方法,会自动消除同步操作。
- 锁粗化案例:
for (int i = 0; i < 1000; i++) {
synchronized(lock) {
doSomething();
}
}
JVM会将循环内的多次加锁合并为单次加锁:
synchronized(lock) {
for (int i = 0; i < 1000; i++) {
doSomething();
}
}
六、synchronized的可见性保障
通过内存屏障实现:
- 在monitorenter指令后插入LoadLoad屏障和LoadStore屏障
- 在monitorexit指令前插入StoreStore和StoreLoad屏障
内存屏障作用:
LoadLoad屏障:禁止读操作重排序
StoreStore屏障:禁止写操作重排序
LoadStore屏障:禁止读后写重排序
StoreLoad屏障:禁止写后读重排序(全能屏障)
七、最佳实践与故障排查
- 死锁检测:
# 生成线程dump
jstack <pid> > thread.dump
# 查找死锁信息
Found one Java-level deadlock:
=============================
"Thread-2":
waiting to lock monitor 0x00007f88d8003d58 (object 0x000000076ab270c0, a java.lang.Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x00007f88d8003cb8 (object 0x000000076ab270d0, a java.lang.Object),
which is held by "Thread-2"
- 锁竞争分析: 使用async-profiler生成火焰图:
./profiler.sh -d 30 -f profile.html <pid>
- 性能优化建议:
- 减小同步代码块粒度
- 避免在循环内加锁
- 对读写场景使用ReadWriteLock
- 使用ThreadLocal避免共享资源
- 考虑使用StampedLock优化读多写少场景
八、与ReentrantLock对比
特性对比表:
特性 | synchronized | ReentrantLock |
---|---|---|
实现机制 | JVM内置 | JDK实现 |
锁获取方式 | 自动获取释放 | 必须显式lock/unlock |
可中断 | 不支持 | lockInterruptibly() |
公平锁 | 非公平 | 可配置公平策略 |
Condition条件 | 单条件 | 多条件队列 |
性能 | 优化后接近 | 高竞争时更优 |
锁绑定多个条件 | 不支持 | 支持多个Condition |
尝试获取锁 | 不支持 | tryLock() |
选择建议:
- 简单同步场景优先使用synchronized
- 需要高级功能时选择ReentrantLock
- 读写分离场景使用ReadWriteLock
- 极高并发考虑StampedLock
正文到此结束
相关文章
热门推荐
评论插件初始化中...