Skip to content

synchronized加锁的原理

约 1124 字大约 4 分钟

多线程与并发美团

2025-04-09

⭐ 题目日期:

美团 - 2025/4/4

📝 题解:

synchronized 是 Java 中实现线程同步的核心机制,其底层原理涉及 对象锁JVM 字节码指令操作系统级互斥。以下是其工作原理的详细分析:


1. 基本概念:锁与监视器(Monitor)

  • 锁的本质:每个 Java 对象都与一个 监视器锁(Monitor) 关联,synchronized 通过竞争对象的 Monitor 实现同步。
  • Monitor 结构
    • Entry Set:存放等待锁的线程。
    • Owner:持有锁的线程。
    • Wait Set:存放调用 wait() 后释放锁的线程。

2. 底层实现:JVM 字节码

  • 修饰代码块

    • 编译后会在代码块前后插入 monitorentermonitorexit 指令。
    • 示例
      public void method() {
          synchronized(obj) {
              // 代码块
          }
      }
      // 字节码反编译结果:
      monitorenter   // 尝试获取锁
      ...             // 同步代码
      monitorexit    // 释放锁
  • 修饰方法

    • 方法会添加 ACC_SYNCHRONIZED 标志,JVM 在方法调用时自动加锁。

3. 锁的存储位置:对象头(Mark Word)

  • 对象内存结构

    • 每个对象头部包含 Mark Word(存储哈希码、GC 年龄、锁状态等)。
    • 锁状态标志位:标识当前对象的锁状态(无锁、偏向锁、轻量级锁、重量级锁)。
  • Mark Word 结构(以 64 位 JVM 为例):

    锁状态Mark Word 内容
    无锁哈希码 + 分代年龄 + 未使用位
    偏向锁线程ID + Epoch + 分代年龄 + 偏向锁标志
    轻量级锁指向栈中锁记录的指针
    重量级锁指向操作系统互斥量(Mutex)的指针

4. 锁升级优化(Java 6+)

为了减少锁操作的开销,JVM 采用 锁膨胀 策略,按竞争激烈程度逐步升级锁:

  1. 偏向锁(Biased Locking)

    • 场景:无实际竞争,只有一个线程访问。
    • 原理:在 Mark Word 中记录线程ID,后续无需 CAS 操作。
    • 优化:消除无竞争下的同步开销。
  2. 轻量级锁(Lightweight Locking)

    • 场景:多个线程交替执行,无并发竞争。
    • 原理
      • 线程栈中创建 锁记录(Lock Record)
      • 通过 CAS 将 Mark Word 复制到锁记录,并尝试将对象头指向锁记录。
    • 失败时:升级为重量级锁。
  3. 重量级锁(Heavyweight Locking)

    • 场景:高并发竞争。
    • 原理:依赖操作系统提供的 互斥量(Mutex),线程进入阻塞状态。
    • 缺点:涉及用户态到内核态切换,开销较大。

5. 锁的获取与释放流程


6. 关键机制

  • 可重入性

    • 同一线程可重复获取同一把锁(通过计数器实现)。
    • 示例
      synchronized void a() { b(); }
      synchronized void b() {} // 允许重入
  • 自旋优化

    • 轻量级锁竞争失败时,线程不会立即阻塞,而是 自旋等待(循环尝试获取锁)。
    • 避免线程切换开销(适用于锁占用时间短的场景)。
  • 适应性自旋(Java 6+):

    • 根据历史自旋成功率动态调整自旋时间。

7. 与其他锁的对比

synchronizedReentrantLock
实现方式JVM 内置,自动管理JDK 实现,需手动释放
锁类型非公平锁(默认)可配置公平/非公平
条件变量通过 wait()/notify()支持多个 Condition
性能优化后接近 ReentrantLock高竞争下更灵活

8. 使用注意事项

  1. 锁对象的选择

    • 避免锁字符串常量、基本类型包装类(易引起意外竞争)。
    • 推荐使用私有不可变对象(如 private final Object lock = new Object())。
  2. 锁粒度控制

    • 尽量缩小同步代码块范围,减少竞争。
  3. 避免死锁

    • 按固定顺序获取多把锁,或使用超时机制(如 tryLock)。

总结

synchronized 通过对象头中的 Mark Word 和 JVM 的 锁升级机制(偏向锁→轻量级锁→重量级锁)实现高效同步。其设计目标是 在无竞争时尽量减少开销,高竞争时保证正确性。理解其底层原理有助于编写更高效、安全的并发代码。