跳到正文
Elaine Blog
返回

synchronized 的实现、锁状态与适用边界

并发编程

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 计数器

同步代码块会出现 monitorentermonitorexit,异常路径也必须释放监视器。同步方法则在方法访问标志中使用 ACC_SYNCHRONIZED,不一定显示这两条指令。

监视器提供两项语义

同一时刻只有一个线程持有对象监视器。释放监视器先行发生于后续线程获取同一监视器,因此锁内写入对后续持锁线程可见。

synchronized 是可重入的:线程已持有监视器时可以再次进入同一监视器保护的代码。可重入避免同一对象的方法互调时 自我死锁,但不会防止不同锁顺序造成的死锁。

不要背固定升级链

旧资料常把 HotSpot 锁描述为“无锁→偏向锁→轻量级锁→重量级锁”的必经路线。这个模型已经过时:

使用目标 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 原子类,理解不用阻塞锁时如何更新单变量状态。


分享这篇文章:

上一篇
JMM、Happens-Before 与三类并发性质
下一篇
CAS、ABA 与 Atomic 原子类