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 ;
}

当线程尝试获取锁时:

  1. 通过CAS操作修改Mark Word
  2. 成功则获得锁
  3. 失败则进入_EntryList队列等待
  4. 持有锁的线程调用wait()后进入_WaitSet
  5. 锁释放时唤醒_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编译器在运行时进行的优化:

  1. 锁消除案例:
public String concat(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb.toString();
}

JVM检测到sb对象没有逃逸出方法,会自动消除同步操作。

  1. 锁粗化案例:
for (int i = 0; i < 1000; i++) {
    synchronized(lock) {
        doSomething();
    }
}

JVM会将循环内的多次加锁合并为单次加锁:

synchronized(lock) {
    for (int i = 0; i < 1000; i++) {
        doSomething();
    }
}

六、synchronized的可见性保障

通过内存屏障实现:

  1. 在monitorenter指令后插入LoadLoad屏障和LoadStore屏障
  2. 在monitorexit指令前插入StoreStore和StoreLoad屏障

内存屏障作用:

LoadLoad屏障:禁止读操作重排序
StoreStore屏障:禁止写操作重排序  
LoadStore屏障:禁止读后写重排序
StoreLoad屏障:禁止写后读重排序(全能屏障)

七、最佳实践与故障排查

  1. 死锁检测
# 生成线程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"
  1. 锁竞争分析: 使用async-profiler生成火焰图:
./profiler.sh -d 30 -f profile.html <pid>
  1. 性能优化建议
  • 减小同步代码块粒度
  • 避免在循环内加锁
  • 对读写场景使用ReadWriteLock
  • 使用ThreadLocal避免共享资源
  • 考虑使用StampedLock优化读多写少场景

八、与ReentrantLock对比

特性对比表:

特性 synchronized ReentrantLock
实现机制 JVM内置 JDK实现
锁获取方式 自动获取释放 必须显式lock/unlock
可中断 不支持 lockInterruptibly()
公平锁 非公平 可配置公平策略
Condition条件 单条件 多条件队列
性能 优化后接近 高竞争时更优
锁绑定多个条件 不支持 支持多个Condition
尝试获取锁 不支持 tryLock()

选择建议:

  • 简单同步场景优先使用synchronized
  • 需要高级功能时选择ReentrantLock
  • 读写分离场景使用ReadWriteLock
  • 极高并发考虑StampedLock
正文到此结束
评论插件初始化中...
Loading...