像素岛沙盒冒险
64.86MB · 2026-02-07
尽管现代并发工具日益丰富,synchronized 仍是 Java 并发体系的基石级存在:
jstack/JFR 直接识别锁状态本文聚焦 JDK 17+ 真实环境,拒绝过时知识,直击实战痛点。
核心问题:为何不直接使用重量级锁?为何要“逐步升级”?
| 锁方案 | 无竞争开销 | 低竞争开销 | 高竞争开销 | CPU/OS 资源消耗 |
|---|---|---|---|---|
| 重量级锁 | 极高(~1500ns) | 高 | 中 | 线程挂起/唤醒(上下文切换) |
| 轻量级锁 | 低(~50ns) | 中 | 极高(自旋空转) | CPU 持续占用 |
| 无锁 | 0 | 0 | 不适用 | 无 |
锁升级的本质是“动态成本最优策略”:
-XX:PreBlockSpin)| 特性 | JDK ≤14 | JDK 15~16 | JDK 17+ |
|---|---|---|---|
| 偏向锁 | 默认启用 | 默认禁用(JEP 374) | 彻底移除(JEP 420) |
| 升级路径 | 无锁→偏向→轻量→重量 | 同左(但偏向锁不生效) | 无锁→轻量→重量 |
| 原因 | 单线程优化 | 撤销成本高,收益低 | 现代应用多线程竞争为主,维护成本 > 收益 |
Lock RecordObjectMonitor_EntryList 阻塞(OS 级挂起)// JIT 可消除锁(sb 为局部变量,无逃逸)
public String concat() {
StringBuffer sb = new StringBuffer(); // 内部 synchronized append
sb.append("a").append("b");
return sb.toString();
}
// 陷阱1:锁字符串字面量(字符串驻留导致全局锁)
private static final String LOCK = "config";
synchronized(LOCK) { ... } // 所有模块使用"config"处互斥!
// 陷阱2:锁自动拆装箱对象(锁对象变更)
Integer count = 0;
synchronized(count) {
count++; // count 指向新 Integer 对象!后续同步失效
}
// JDK 17+ 推荐写法
private final Object lock = new Object(); // final 确保引用不变
public void safeMethod() {
synchronized(lock) {
// 临界区逻辑
}
}
synchronized(lock) {
while (!conditionMet) { // 必须用 while!
lock.wait(); // 释放锁,进入 WaitSet
}
// 执行业务
lock.notifyAll(); // 优先 notifyAll 避免遗漏
}
| 陷阱 | 风险 | JDK 17+ 解决方案 |
|---|---|---|
| 静态/实例方法混用 | 锁对象不同(Class vs this) | 显式注释锁范围,统一规范 |
| 子类覆盖未加锁 | 父类同步逻辑被绕过 | 子类方法显式添加 synchronized |
| 锁内执行耗时 I/O | 阻塞线程池,吞吐骤降 | 提前校验,锁外执行 I/O |
| 场景 | 为什么不能用 synchronized | 推荐方案 | 原因说明 |
|---|---|---|---|
| 需要中断等待 | wait 可中断,但锁等待不可中断 | ReentrantLock.lockInterruptibly() | 长时间阻塞需响应取消信号 |
| 需超时获取锁 | 无超时机制,易死锁 | ReentrantLock.tryLock(timeout) | 避免线程永久挂起 |
| 需公平锁策略 | 始终非公平,可能线程饥饿 | new ReentrantLock(true) | 保障请求顺序(如交易系统) |
| 多条件变量同步 | 仅1个 wait set | ReentrantLock + 多 Condition | 生产者-消费者等复杂模型 |
| 读多写少且读写分离 | 读写互斥,吞吐瓶颈 | ReentrantReadWriteLock | 读操作并发提升 5-10 倍 |
| 分布式系统跨 JVM | 仅限单 JVM 内部 | Redisson / ZooKeeper 锁 | 服务集群需全局协调 |
| 高性能计数器(高竞争) | 重量级锁开销大 | LongAdder / Striped64 | 无锁分段累加,吞吐提升百倍 |
| 需锁状态监控埋点 | 无法获取持有者/等待队列信息 | ReentrantLock.getQueueLength() | 运维监控、动态降级需求 |
// 伪代码:选择逻辑
if (需要中断 || 需要超时 || 需要公平锁 || 多条件变量) {
选 ReentrantLock;
} else if (读 >> 写 && 读写可分离) {
选 ReadWriteLock;
} else if (单变量原子操作) {
选 AtomicXXX / LongAdder;
} else if (跨 JVM) {
选分布式锁;
} else {
// 90% 场景:选 synchronized(安全、简洁、JVM 优化友好)
synchronized(lock) { ... }
}
| 维度 | synchronized | ReentrantLock | Atomic/LongAdder |
|---|---|---|---|
| 自动释放 | (编译器保障) | (需 finally unlock) | N/A |
| JVM 优化 | (锁消除/粗化) | ( intrinsic 优化) | |
| 诊断友好度 | (jstack/JFR 直接可见) | ️(需代码埋点) | |
| 高竞争吞吐 | 中(重量级锁开销) | 中(可调优) | 极高(LongAdder 分段) |
| 适用场景 | 通用同步、低竞争、代码简洁优先 | 需高级特性、高竞争精细控制 | 计数器、状态标志 |
private final Object lock = new Object();(杜绝非 final 引用)ConcurrentHashMap、CopyOnWriteArrayList、LongAdderwhile 判断 + notifyAll# 1. 死锁检测(直接定位)
jstack <pid> | grep -A 20 "deadlock"
# 2. JFR(Java Flight Recorder)监控锁竞争
java -XX:+FlightRecorder -XX:StartFlightRecording=duration=60s,filename=recording.jfr MyApp
# 使用 JDK Mission Control 分析 "Java Monitor Blocked" 事件
# 3. 锁粗化效果验证(需开启)
-XX:+PrintEliminateLocks # 观察锁消除日志
| 项目 | 说明 |
|---|---|
| 偏向锁 | 已彻底移除,无需关注相关配置与行为 |
| 锁升级起点 | 所有同步操作起点为轻量级锁 |
| 推荐 JVM 参数 | 无需特殊配置;高并发服务可微调 -XX:PreBlockSpin(默认自适应) |
| 逃逸分析 | 依然生效,合理编写代码可触发锁消除(局部对象同步) |
锁升级是“成本动态平衡术”
轻量级锁(低开销)与重量级锁(保系统)的智能切换,是 JVM 对并发场景的深度理解。无需手动干预,信任 JVM 自适应机制。
synchronized 是“安全网”,非“万能锤”
JDK 17+ 是新起点
偏向锁移除标志着 JVM 向“现代多线程应用”全面对齐。聚焦轻量级锁优化逻辑,摒弃过时认知。
正确姿势:
1️⃣ 优先用 synchronized 保证逻辑正确
2️⃣ 通过 JFR/Arthas 实测锁竞争热点
3️⃣ 仅在明确需求+数据支撑下替换为高级锁