美团一面:说说synchronized的实现原理?

125 阅读3分钟

在Java中,synchronized是用于实现线程同步的关键字,其底层实现原理涉及对象头、监视器锁(Monitor)以及锁升级机制。以下是详细解析:


1. 对象头与Mark Word

每个Java对象在内存中由三部分组成:

  • 对象头(Header) :存储对象的元数据,包括锁状态、GC分代年龄等。
  • 实例数据(Instance Data) :对象的成员变量。
  • 对齐填充(Padding) :确保对象内存对齐。

Mark Word是对象头的一部分,记录了对象的锁状态、哈希码、分代年龄等信息。在32位JVM中,Mark Word占4字节;64位JVM中占8字节。不同锁状态下,Mark Word的内容会动态变化:

锁状态存储内容
无锁哈希码、分代年龄、是否偏向锁(0)
偏向锁线程ID、Epoch、分代年龄、偏向标记(1)
轻量级锁指向栈中锁记录的指针(Lock Record)
重量级锁指向Monitor的指针

2. 监视器锁(Monitor)

MonitorJVM实现的线程同步机制,每个对象关联一个Monitor。当线程执行synchronized代码块时:

  1. 尝试获取锁:通过CAS操作修改Mark Word,指向当前线程的Monitor。
  2. 获取成功:线程持有锁,执行同步代码。
  3. 获取失败:线程进入阻塞队列,等待锁释放后被唤醒。

3. 锁升级机制

为了提高性能,JVM在JDK 6后引入锁升级机制,按竞争激烈程度逐步升级:

  1. 偏向锁(Biased Lock)

    • 适用场景:单线程重复访问同步代码。
    • 实现:Mark Word记录线程ID,后续无需CAS操作。
    • 升级条件:检测到其他线程尝试获取锁(撤销偏向锁)。
  2. 轻量级锁(Lightweight Lock)

    • 适用场景:多线程交替执行,无实际竞争。
    • 实现:通过CAS将Mark Word替换为指向线程栈中锁记录的指针。
    • 升级条件:CAS自旋失败(竞争加剧)。
  3. 重量级锁(Heavyweight Lock)

    • 适用场景:高并发竞争。
    • 实现:线程阻塞,依赖操作系统互斥量(mutex)实现同步,开销较大。

4. 同步代码的字节码实现

  • 同步代码块:通过monitorentermonitorexit指令实现。

    public void syncBlock() {
        synchronized (this) { // monitorenter
            // 代码逻辑
        } // monitorexit
    }
    
  • 同步方法:通过方法访问标志ACC_SYNCHRONIZED实现。

    public synchronized void syncMethod() {
        // 代码逻辑
    }
    

5. 优化机制

  • 锁消除(Lock Elimination) :JIT编译器检测到不可能存在共享数据竞争时,自动移除锁。
  • 锁粗化(Lock Coarsening) :将多个连续的锁操作合并为一个,减少锁开销。
  • 自适应自旋(Adaptive Spinning) :根据历史自旋成功率动态调整自旋次数,避免CPU空转。

6. 性能对比

锁类型适用场景性能开销实现依赖
偏向锁单线程无竞争极低CAS
轻量级锁低并发交替执行CAS + 自旋
重量级锁高并发竞争操作系统互斥量(mutex)

总结

  • synchronized通过对象头、Monitor和锁升级机制实现线程同步。
  • 锁升级策略(偏向锁→轻量级锁→重量级锁)平衡了性能和安全性。
  • 优化机制(锁消除、锁粗化)进一步减少同步开销。

扩展思考

  • 在高并发场景下,为何最终会升级为重量级锁?
  • synchronizedReentrantLock有何异同?