synchronized- 并发

⬅️ 03-volatile深度解析 | ➡️ 05-CAS与原子类


1. 是什么:管程(Monitor)模型(⭐⭐)

1.1 语义

synchronized 提供 互斥性 + 可见性 + 有序性

  • 互斥:同一时刻只有一个线程持有监视器锁
  • 可见unlock hb 后续同锁的 lock → 锁内写对后续同锁内读可见
  • 有序:临界区内的操作不会被重排到临界区外

1.2 三种用法

java 复制代码
// 1. 实例方法 → 锁 this
synchronized void method() { ... }

// 2. 静态方法 → 锁 Class 对象
static synchronized void method() { ... }

// 3. 代码块 → 锁任意对象
synchronized (lockObj) { ... }

1.3 字节码视角

arduino 复制代码
monitorenter     ← 获取锁
// 临界区
monitorexit      ← 释放锁
monitorexit      ← 异常路径也保证释放(编译器自动插入)

2. 为什么需要 synchronized(⭐⭐)

volatile 只能保证可见性 ,不能保证互斥复合操作的原子性

java 复制代码
volatile int count = 0;
count++;  // ❌ 不安全

synchronized (this) {
    count++;  // ✅ 安全:读-改-写在临界区内原子执行
}

3. 底层实现:对象头、锁升级与 CPU 指令(⭐⭐⭐)

3.1 对象头与 Mark Word

Java 对象的前 8 字节(64 位 JVM)是 Mark Word,存储锁状态、hashCode、GC 年龄:

css 复制代码
┌──────────────────────────────────────┬──────┬───┐
│              Mark Word (62 bits)      │ age  │ 状态│
├──────────────────────────────────────┼──────┼───┤
│ unused:25 | hashCode:31 | age:4 | 0  │  01  │无锁│
│ threadId:54 | epoch:2 | age:4 | 1    │  01  │偏向│
│ Lock Record 指针:62                  │  00  │轻量│
│ ObjectMonitor 指针:62                │  10  │重量│
│ (GC)                                 │  11  │ GC │
└──────────────────────────────────────┴──────┴───┘

3.2 锁升级路径

flowchart LR A["无锁"] --> B["偏向锁
同线程零开销重入"] B -->|"其他线程竞争"| C["轻量级锁
CAS 自旋"] C -->|"自旋失败/竞争激烈"| D["重量级锁
OS mutex / futex"] style A fill:#e8f5e9 style B fill:#fff3e0 style C fill:#fce4ec style D fill:#f44336,color:#fff

注意 :升级是单向的(偏向→轻量→重量),不会降级(重量→偏向)。JDK 15+ 默认关闭偏向锁。

3.3 偏向锁指令级

asm 复制代码
; 快速路径:检查 Mark Word 中的线程 ID
mov    rax, QWORD PTR [rdi]        ; 读 Mark Word
and    rax, biased_lock_mask        ; 提取偏向标志
cmp    rax, current_thread_id       ; 是否偏向当前线程?
je     already_biased               ; ✅ 同线程重入:无原子操作,~1 ns
; ❌ 否则 → 偏向撤销(可能触发 safepoint STW)

撤销触发点

  • 另一个线程尝试获取
  • 批量重偏向/撤销阈值(默认 20/40)
  • 调用 hashCode()(某些实现)

3.4 轻量级锁指令级

asm 复制代码
; 栈上分配 Lock Record,CAS 替换 Mark Word
lea    rdx, [rsp+lock_record_offset]  ; Lock Record 地址
mov    rax, QWORD PTR [rdi]           ; 读当前 Mark Word
mov    QWORD PTR [rdx], rax           ; 保存旧 Mark Word
lock   cmpxchg QWORD PTR [rdi], rdx   ; CAS:Mark Word → Lock Record 指针
jne    slow_path                       ; 失败 → 自旋或膨胀到重量级

3.5 重量级锁与 OS 路径

asm 复制代码
; 膨胀(inflate)后的 ObjectMonitor::enter
; 快速 CAS → 失败 → 自旋 → 失败 →
; 最终调用 OS 挂起:
syscall                              ; futex(addr, FUTEX_WAIT, ...)
;       ↑ 用户态 → 内核态上下文切换

3.6 成本量化

锁状态 指令数 上下文切换 延迟
偏向(同线程) 3-5 ~1 ns
轻量(CAS 成功) 10-20 ~10-30 ns
轻量(自旋) 百-千 ~100-1000 ns
重量(futex) N/A 用户↔内核 ~5000-15000 ns

4. JIT 锁优化(⭐⭐)

4.1 锁消除

java 复制代码
void method() {
    Object lock = new Object();  // 逃逸分析:lock 不逃逸
    synchronized (lock) {         // JIT 直接删除锁!
        // ...
    }
}

4.2 锁粗化

java 复制代码
for (int i = 0; i < 1000; i++) {
    synchronized (lock) {     // JIT 合并为一次加锁
        list.add(i);
    }
}
// → 优化为一次 synchronized + 循环

4.3 自适应自旋

JVM 根据上次自旋成功率动态调整自旋次数------上次成功则多转几圈,上次失败则直接挂起。


5. wait/notifyCondition(⭐⭐)

5.1 wait/notify 三件套

java 复制代码
synchronized (lock) {
    while (!condition) {  // 必须用 while(防伪唤醒)
        lock.wait();      // 释放锁 + 挂起
    }
    // 条件满足,处理
}

synchronized (lock) {
    condition = true;
    lock.notify();        // 唤醒一个等待线程(不释放锁)
}

5.2 与 Condition 对比

维度 wait/notify Condition
绑定 任何对象的监视器 绑定 Lock
条件队列 1 个 wait-set 多个 Condition
可中断等待 不支持 awaitUninterruptibly() / await()
限时等待 wait(timeout) await(time, unit) + awaitNanos
公平性 无保证 随 Lock 策略

6. 面试题精选(⭐⭐ ~ ⭐⭐⭐)

Q1 🟦 字节:「对象头 64 位里有哪些状态?偏向锁撤销触发点?」(⭐⭐⭐)

Mark Word 编码 5 种状态(无锁/偏向/轻量/重量/GC),由最低 2-3 位区分。

偏向撤销触发点:

  1. 另一个线程尝试偏向同一对象
  2. 批量重偏向阈值(默认 20 次)/ 批量撤销阈值(40 次)
  3. 调用 hashCode()(部分路径会撤销偏向,因为 hashCode 需要占据 Mark Word 空间)

追问 :为什么 wait() 让重偏向失效?------ wait 导致 monitor inflate(膨胀为重量级),之后偏向位被清除。

Q2 🟧 阿里:「线上 CPU 不高但 RT 抖动,怀疑偏向锁,怎么验证与止血?」(⭐⭐⭐)

  1. 取证 :火焰图 + JFR jdk.JavaMonitorEnter 事件 / -XX:+PrintBiasedLocking(旧版本)
  2. 原理 :多线程轻微竞争 → 偏向撤销进 safepoint → P99 尖刺(均值 CPU 不高)
  3. 止血 :灰度 -XX:-UseBiasedLocking,监控 P99 变化
  4. 案例:某网关 JDK8,撤销峰值 12k/s → P99 从 18ms 飙到 120ms;关闭偏向后 P99 回 22ms,CPU P95 仅 +6%

追问:JDK 17 默认策略?------ 偏向锁默认关闭(JEP 374),面试要确认目标 JDK 版本。

Q3 🟧 阿里:「static synchronized 和实例 synchronized 的区别?」(⭐⭐)

  • static synchronized:锁的是 Class 对象
  • 实例 synchronized:锁的是 this 实例
  • 两者不互斥(不同监视器对象)
  • 死锁风险:如果同时使用,注意锁排序统一

Q4 🟡 美团:「用 synchronized 还是 ReentrantLock?」(⭐⭐)

维度 synchronized ReentrantLock
条件队列 1 个 多个 Condition
可中断获取 lockInterruptibly
限时获取 tryLock(timeout)
公平锁 ✅ 构造参数
释放保证 字节码保证 必须 finally
性能 JDK6+ 接近 略好(极高竞争下)

原则 :简单同步用 synchronized;需要高级特性(中断/限时/多条件/公平)用 ReentrantLock

Q5 🟢 腾讯:「synchronized ("LOCK") 有什么问题?」(⭐)

:字符串字面量 intern → 全 JVM 共享同一对象 → 可能与不相关的代码意外互斥 → 性能或死锁。永远用 private final Object lock = new Object() 做监视器。


7. 快问快答(⭐)

  1. synchronized 可重入吗?------ 可。
  2. synchronized 释放锁后其他线程能立即看到修改吗?------ 遵循 hb:同锁的 unlock hb 后续 lock。
  3. Thread.sleep 释放锁吗?------ 不释放
  4. wait 释放锁吗?------ 释放(被唤醒后重新获取)。
  5. notify 和 notifyAll 区别?------ notify 唤一个(随机),notifyAll 唤全部但仍串行获取锁。
  6. 锁消除什么时候触发?------ 逃逸分析证明锁对象不逃逸。
  7. synchronized 和 volatile 能替换吗?------ 不能互换:volatile 无互斥,synchronized 更重。
  8. 字节码保证释放是怎么做到的?------ 编译器在异常路径插入额外 monitorexit
  9. Thread.holdsLock(obj) 作用?------ 断言当前线程持有 obj 的锁。
  10. 嵌套 synchronized 有死锁风险吗?------ 有,如果锁顺序不一致。

8. 本章小结

flowchart TD A["语义
互斥+可见+有序"] --> B["对象头
Mark Word 编码"] B --> C["锁升级
偏向→轻量→重量"] C --> D["CPU 指令
CAS / futex"] D --> E["JIT 优化
消除/粗化/自旋"] E --> F["面试落地
选型 + 事故排查"]

下一步:synchronized 的互斥靠 ,那有没有不用锁 的方式实现原子性?→ 05-CAS与原子类

相关推荐
martian6651 小时前
在 IntelliJ IDEA 中安装、配置 Claude Code 及解决连接错误完全指南
java·ide·intellij-idea
代码柏拉图1 小时前
AI时代如何提问面试者
人工智能·面试·职场和发展
lalala_Zou1 小时前
某大厂后端一面
java·开发语言
爱笑的源码基地1 小时前
拿来即用:基于Spring Cloud+UniApp的智慧工地源码,架构清晰易扩展
java·云计算·源码·智慧工地·程序·开箱即用·数字工地
Kiyra1 小时前
Interview Agent:从面试平台到 Agent 工程实战的进化之路
面试·职场和发展
WL_Aurora1 小时前
Java技术体系:JDK、JRE、JVM的关系与演进(2026最新版)
java·开发语言·jvm
砚底藏山河2 小时前
股票数据API接口:(沪深A股)如何获取股票当天逐笔交易数据
java·windows·python·maven
小江的记录本2 小时前
【MySQL】MySQL日志体系:redo log/undo log/binlog 三者区别、两阶段提交、如何保证数据一致性
java·数据库·后端·python·sql·mysql·面试
赛特·亮2 小时前
利用WTAPI(WeChatapi)-robot-go 项目解析与实战指南
微信·面试·golang