JJava 知识库
JAVA INTERVIEW

高频面试题

Java多线程进阶约 3 分钟

高并发扣减同一个商品库存时,你会用 synchronized 还是 ReentrantLock?锁粒度怎么定?

参考回答约 3 分钟 · 口语表达
先说结论

先说结论:理解监视器锁、显式锁、可重入、公平性以及 AQS 同步队列

01

我先给结论,再说明它在项目里解决什么问题。普通互斥同步优先使用 synchronized,语法简单且能自动释放锁。需要可中断获取、超时尝试、公平锁或多个条件队列时,使用 ReentrantLock。 显式锁必须在 finally 中释放,否则异常会造成永久占锁。

02

核心机制我会按一次真实执行过程来讲。沿着「线程尝试获取状态 → CAS 修改同步状态 → 失败后进入 CLH 队列 → 前驱释放并唤醒 → 节点重新竞争成功」观察输入、状态与输出,这些阶段都可以从日志、指标或源码里验证。 线程尝试获取状态 AQS 用一个 volatile state 表示同步状态,具体子类解释 0/1、重入次数或许可数量。 CAS 修改同步状态 tryAcquire 由同步器实现,通过 CAS 或独占规则决定当前线程是否成功。

03

实现细节只抓关键入口,不会整段背源码。AbstractQueuedSynchronizer#acquire:tryAcquire、入队与 park。 AbstractQueuedSynchronizer#release:state 释放与 unparkSuccessor。

04

放到生产使用时,我会关注参数和验证数据。用 JFR Java Monitor Blocked/Thread Park 对比公平与非公平锁;注入中断、超时和取消验证队列可前进。 固定输入和基线 先在没有故障注入的环境执行上述配置,固定数据规模、并发度、运行时版本和预热时间。以「持锁 P99」为主基线,记录值应满足「短于 1ms 示例基线」;同时保存 锁等待时间、AQS 队列长度,使后续变化能够回到同一时间轴比较。

05

最后补充常见误区和使用边界。同步器在 release 时只修改 state,没有在状态变为可用时返回 true,AQS 因此不唤醒后继节点。修复需要遵守 acquire/release 返回值契约,并用中断、取消和超时场景测试队列清理。 忘记 finally unlock 造成永久占用:锁等待时间:I/O 移出锁内。 方案:更适合的场景:主要收益:代价与边界。 synchronized:普通互斥且不需高级能力:JVM 优化成熟、语法简单:不支持多个条件与可中断获取。 ReentrantLock:需公平、超时、可中断或多个 Condition:能力完整、观测更直接:必须 finally 解锁。 自定义 AQS:同步语义无法由现有工具表达:复用排队与唤醒框架:正确性复杂,维护和测试成本高。