⭐ 推荐:社区规则条款 V1.0

Java 中 synchronized 和 Lock 该怎么选?

chinaz
chinaz 初级会员超兽战士
发布于 2026-10-12 00:29 ·1 浏览 ·0 回复
内容摘要

Java 中短小互斥且无需超时、中断时用 synchronized;需要 tryLock、超时、可中断、公平锁、多 Condition、读写分离或 JDK 21~23 虚拟线程时用 ReentrantLock。

学完这篇,你能拿到一份可执行的判断清单:面对一段 Java 并发代码时,知道该用 synchronized 还是 java.util.concurrent.locks.Lock(实际几乎都是 ReentrantLock),以及写完怎么验证没选错。

第一步:认清两者的定位

这一步要弄清它们分别是什么、从哪个版本开始可用,结果是你不会再把它俩当成「性能高低二选一」。

  • synchronized 是关键字,由 JVM 实现,JDK 1.0 就有。它锁定的是对象监视器(monitor)。
  • Lock 是接口,位于 java.util.concurrent.locks 包,JDK 5 引入。日常用的是实现类 ReentrantLock。
  • 锁优化(偏向锁、轻量级锁)从 JDK 6 开始进入 synchronized,所以「synchronized 一定慢」是 JDK 5 时代的旧结论。偏向锁在 JDK 15 起默认禁用(JEP 374),不用再纠结它。

注意:JDK 21 的虚拟线程里,synchronized 块会把载体线程 pin 住,可能拖垮吞吐;JDK 24(JEP 491)已解除该限制。如果你在 JDK 21~23 上跑虚拟线程,优先用 ReentrantLock。

第二步:把差异列成对照表

这一步把选型依据固定下来,结果是你能对着表逐条打勾。

能力synchronizedReentrantLock
获取方式进入代码块自动获取手动 lock()
释放方式退出代码块自动释放必须 finally 里 unlock()
尝试获取不阻塞不支持tryLock()
带超时获取不支持tryLock(long, TimeUnit)
可中断获取不支持lockInterruptibly()
公平锁不支持new ReentrantLock(true)
多个条件队列一个(wait/notify)多个 Condition
读写分离不支持ReentrantReadWriteLock

第三步:按场景做选择

这一步是决策动作,结果是你直接得到答案。

满足下面任意一条,选 ReentrantLock:

  1. 需要「拿不到锁就先干别的」:调 tryLock(),返回 false 就跳过。
  2. 需要超时:lock.tryLock(3, TimeUnit.SECONDS),超时走降级逻辑。
  3. 需要可取消:lockInterruptibly(),线程被 interrupt() 时抛 InterruptedException。
  4. 需要精确唤醒:生产者消费者用两个 Condition,notFull.await() 和 notEmpty.signal() 分开。
  5. 需要公平锁:new ReentrantLock(true),按等待顺序发放。
  6. 读多写少:用 ReentrantReadWriteLock,读锁共享、写锁互斥。

其余情况——只要一段短小的互斥逻辑、不需要超时和中断——直接用 synchronized,代码短、不会忘解锁。

第四步:写两段等价代码对照

这一步把模板抄下来,结果是你能马上替换现有代码。

// 方式一:synchronized
public synchronized void add(int n) {
    this.count += n;
}

// 方式二:ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
public void add(int n) {
    lock.lock();
    try {
        this.count += n;
    } finally {
        lock.unlock();
    }
}

lock() 放在 try 外面第一行,unlock() 放 finally,这两条不能改。

注意:unlock() 千万别写在 try 内部结尾,一旦中间抛异常锁就永远不释放。也不要吞掉 InterruptedException,至少要 Thread.currentThread().interrupt() 还原中断标记。

第五步:验证你的选择

这一步确认线上不会卡死,结果是你能定位问题。

  • 怀疑死锁:命令行执行 jstack <pid>,或 jcmd <pid> Thread.print,搜 waiting to lock 和 parking to wait for。
  • 想看锁竞争:启动时加 -XX:+UnlockDiagnosticVMOptions,用 JFR 录制 jcmd <pid> JFR.start duration=60s filename=lock.jfr。
  • 做性能对比:用 JMH,别用 System.currentTimeMillis() 手写循环,JIT 会把你的测量结果优化掉。
  • 公平锁一定要测吞吐,new ReentrantLock(true) 通常比非公平慢,只在确实需要避免饥饿时开。

小结

  • 默认先用 synchronized,短小互斥它更安全,不会忘 unlock。
  • 需要 tryLock、超时、可中断、多个 Condition、公平锁、读写分离时,换 ReentrantLock。
  • ReentrantLock 的固定模板是 lock() 在 try 外、unlock() 在 finally 里。
  • JDK 21~23 跑虚拟线程时避开 synchronized 长临界区,JDK 24 起此限制已解除。
  • 选完用 jstack 和 JMH 验证,不要凭感觉。
版权声明:本文来自 GJ站长论坛《Java 中 synchronized 和 Lock 该怎么选?》
原文链接:https://www.gj0.com/thread-1678.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~