synchronized 同时提供互斥和 happens-before。它适合结构化、短小的临界区;需要可中断获取、超时、多条件等待或
非块状加锁时,使用 Lock 更合适。现代 JDK 不应再套用固定的“偏向锁升级链”解释所有执行。
Table of contents
Open Table of contents
从字节码确认锁边界
编译下面的类并查看字节码:
final class 计数器 {
private int value;
void increment() {
synchronized (this) {
value++;
}
}
}
javac --release 21 计数器.java
javap -c -p 计数器
同步代码块会出现 monitorenter 和 monitorexit,异常路径也必须释放监视器。同步方法则在方法访问标志中使用
ACC_SYNCHRONIZED,不一定显示这两条指令。
监视器提供两项语义
同一时刻只有一个线程持有对象监视器。释放监视器先行发生于后续线程获取同一监视器,因此锁内写入对后续持锁线程可见。
synchronized 是可重入的:线程已持有监视器时可以再次进入同一监视器保护的代码。可重入避免同一对象的方法互调时
自我死锁,但不会防止不同锁顺序造成的死锁。
不要背固定升级链
旧资料常把 HotSpot 锁描述为“无锁→偏向锁→轻量级锁→重量级锁”的必经路线。这个模型已经过时:
- 偏向锁在 JDK 15 默认禁用并已从后续实现移除。
- HotSpot 的轻量级锁实现和默认模式随 JDK 演进。
- 竞争、等待和 JVM 启发式可能让监视器膨胀,但具体对象头位模式属于实现细节。
使用目标 JDK 的日志和 JFR 观察实际竞争:
java -Xlog:monitorinflation=debug -XX:StartFlightRecording=settings=profile,filename=locks.jfr App
不要根据文章里的对象头图直接推断生产锁成本。
缩小临界区但保持不变量
把慢 I/O 放在锁外通常能减少竞争,但不能拆断“检查后更新”的整体原子性。正确做法是先在锁内生成不可变快照,再在锁外 执行网络或磁盘操作;若外部操作结果需要反写,应设计版本号或状态转换,防止过期结果覆盖新状态。
锁对象必须是私有且稳定的:
private final Object lock = new Object();
不要锁字符串字面量、装箱整数、公开参数或可被替换的字段,这些对象可能被无关代码共享。
选择 synchronized 还是 Lock
普通互斥优先 synchronized,语法会在退出时自动释放。以下需求再考虑 ReentrantLock:带超时的 tryLock、可中断
获取、多个 Condition、公平策略或跨方法锁协议。显式锁必须在 finally 中释放。
下一步
阅读CAS、ABA 与 Atomic 原子类,理解不用阻塞锁时如何更新单变量状态。