(19)《synchronized》

2026-03-21
2
-
- 分钟
|

背景

JDK 6+ 对 synchronized 进行了深度优化(如偏向锁、轻量级锁),在无竞争/低竞争场景下性能甚至优于 CAS

  • 为什么?
    CAS 需要多次重试(CPU 空转),而 synchronized 通过锁升级机制,避免了无效重试。
    (类比:等红灯时,车不熄火(自旋)比熄火等待(阻塞)更高效,尤其在多核CPU上)

前置知识:对象头 & Mark Word

每个 Java 对象都有 对象头(内存中前 8-16 字节):

  • Mark Word:存储对象状态(哈希码、分代年龄等)。
    加锁时,Mark Word 被替换为锁信息(如线程ID、锁记录指针):

    锁状态Mark Word 内容
    无锁哈希码、分代年龄
    偏向锁线程ID、偏向时间戳
    轻量级锁指向锁记录(Lock Record)的指针
    重量级锁指向重量级锁(OS互斥量)的指针

💡 关键:锁升级是 单向的(无锁 → 偏向锁 → 轻量级锁 → 重量级锁),不可逆


锁升级过程(核心!)

1. 偏向锁(Biased Locking)
  • 适用场景无竞争(只有单线程访问)。

  • 优化点:避免每次重入都 CAS。

    • 第一次加锁:用 CAS 将线程ID写入 Mark Word。
    • 后续重入:直接检查 Mark Word 中的线程ID是否匹配 → 无需 CAS
  • 撤销条件

    • 有竞争 → 升级为轻量级锁(需 STW,暂停所有线程)。
    • 访问 hashCode() → 撤销偏向锁(因哈希码会覆盖 Mark Word)。
  • 重偏向:对象被多个线程访问但无竞争时,可重偏向给新线程(批量操作,以类为单位)。

  • 禁用-XX:-UseBiasedLocking(JVM 参数)。

    📌 比喻:课本占座(线程A占座后,自己回来直接用,不用问别人)。


2. 轻量级锁(Lightweight Locking)
  • 适用场景无竞争或少量竞争(线程交替执行,如 A 上课 → B 上课)。

  • 机制

    • 每个线程栈帧中存 Lock Record(锁记录),包含对象 Mark Word 的副本。
    • 加锁:用 CAS 将对象 Mark Word 指向 Lock Record。
      • 成功:无竞争,继续执行。
      • 失败:升级为重量级锁(锁膨胀)。
  • 无竞争示例(线程A、B交替执行):

    Object lock = new Object();
    // 线程A:第一次加锁,CAS成功(Mark Word → Lock Record)
    synchronized (lock) { /* 代码 */ } // 无竞争时,直接执行
    
    // 线程B:尝试加锁,CAS成功(Mark Word → 自己的Lock Record)
    synchronized (lock) { /* 代码 */ } // 无竞争,继续执行
    
  • 锁膨胀示例(竞争发生 → 升级重量级锁):

    Object lock = new Object();
    // 线程A:成功加锁(轻量级锁)
    synchronized (lock) { 
        // 线程B同时尝试加锁,CAS失败 → 触发锁膨胀
        synchronized (lock) { /* 线程B升级为重量级锁 */ }
    }
    

📌 比喻:线程A占座(课本),回来发现课本没动 → 继续上课(无竞争);
有人动了课本 → 线程A立刻升级为铁栅栏(重量级锁)。


3. 重量级锁(Heavyweight Locking)
  • 适用场景高竞争(多个线程同时抢锁)。
  • 优化:自旋锁(JDK 6+)
    • 自旋:线程忙等(CPU 空转),等待锁释放(避免阻塞/唤醒开销)。
    • 自适应
      • 之前自旋成功 → 多自旋几次(如 10 次);
      • 之前自旋失败 → 少自旋甚至不自旋。
    • 注意事项
      • ✅ 多核 CPU 有效(自旋占用 CPU,单核浪费)。

      • ❌ 无法手动控制(JDK 7+ 无法关闭)。

        📌 比喻:等红灯时,车不熄火(自旋)→ 等到绿灯直接走;
        车熄火(阻塞)→ 等绿灯再启动(浪费时间)。


其他关键优化

优化方式原理示例
减少上锁时间同步块内代码越短越好(避免 I/O、网络等耗时操作)。synchronized { shortOp(); }
减少锁粒度拆分大锁为多个小锁(提升并发度)。ConcurrentHashMap(分段锁)
LongAdder(base + cells 数组,多线程并行累加)
锁粗化多次同步 → 合并为一次(JVM 自动优化)。for (i=0; i<10; i++) { sync(); }sync() { for (i=0; i<10; i++) { } }
锁消除JVM 逃逸分析:锁对象是局部变量(不被其他线程访问)→ 移除锁。void method() { Object lock = new Object(); sync(lock) { ... } } → 无锁
读写分离读操作无锁,写操作加锁(写时复制)。CopyOnWriteArrayList(写时复制数组,读无锁)

为什么优化这么重要?

  • 无竞争:偏向锁 → 零 CAS 开销。

  • 低竞争:轻量级锁 → CAS 成功,避免 OS 调用。

  • 高竞争:自旋锁 → 用 CPU 换时间(比阻塞快)。

    💡 总结:JVM 用“锁升级+自适应优化”动态匹配竞争场景,让 synchronized 在大多数场景下比 CAS 更高效

📌 附:锁升级流程图
无锁 → 偏向锁(无竞争)→ 轻量级锁(少量竞争)→ 重量级锁(高竞争)
(箭头方向:单向,不可回退)


笔记原则:只保留核心逻辑,省略冗余代码。
建议:实际编码中,优先用 synchronized(JVM 优化好),少用 CAS(需手动处理重试)。

评论交流

文章目录