多数场景用 synchronized 更优,仅当需 tryLock、响应中断、多条件队列或锁监控时才选 ReentrantLock;AtomicInteger 仅适用于单变量无冲突更新;DB 锁与应用锁混用须对齐粒度并警惕事务边界;StampedLock 因不支持重入和易漏验证,业务中极少使用。

Java里什么时候该用 synchronized 而不是 ReentrantLock
多数场景下,synchronized 更轻量、更安全,JVM 层面优化成熟,比如锁消除、锁粗化、偏向锁(JDK 15+ 默认禁用,但仍有影响)。除非你需要 ReentrantLock 特有的能力,否则别过早切换。
必须用 ReentrantLock 的典型情况:
- 需要尝试获取锁但不阻塞:
lock.tryLock()或带超时的tryLock(long, TimeUnit) - 需要响应中断:
lock.lockInterruptibly() - 需要多个条件队列(
Condition)配合,比如生产者-消费者中区分「空」和「满」等待 - 需要监控锁状态(如
getHoldCount()、isLocked()),调试或诊断时有用
注意:ReentrantLock 是显式锁,lock() 后必须在 finally 块中调用 unlock(),漏写会导致死锁。而 synchronized 是 JVM 自动释放,无此风险。
AtomicInteger 和 CAS 实现乐观锁的边界在哪
AtomicInteger 底层靠 Unsafe.compareAndSwapInt(即 CAS)实现无锁更新,本质是乐观锁思路:假设无冲突,失败再重试。但它只适用于单个变量的原子更新,不能覆盖多字段、跨对象或含业务逻辑的复合操作。
立即学习“Java免费学习笔记(深入)”;
常见误用:
- 用
getAndIncrement()更新计数器没问题,但想「先查再改」(如余额 > 100 才扣款)就不能只靠AtomicInteger,因为读取和判断之间可能被其他线程修改 - 试图用多个
AtomicReference组合模拟事务,结果出现 ABA 问题(值从 A→B→A,CAS 误认为未变),需配合AtomicStampedReference或版本号 - 高竞争下 CAS 失败频繁重试,CPU 占用飙升,吞吐反而不如加锁——这时悲观锁更稳
简单示例:下面这段代码看似原子,实则不是「业务原子」:
int current = counter.get();
if (current > 100) {
counter.set(current - 100); // 中间可能已被其他线程改过
}数据库行锁 + Java 应用层锁混用时最容易踩的坑
典型错误是「以为数据库锁了,应用就安全了」,结果在事务外或事务未提交前,Java 层又做了非原子操作。
例如:用 SELECT ... FOR UPDATE 锁住某订单行,但在事务内又调用了外部 HTTP 接口,接口耗时长,导致数据库锁持有太久,拖垮整个 DB 连接池;或者事务提交后,才去发 MQ 消息,此时若消息发送失败,业务状态已不可逆。
关键原则:
- 数据库锁只保护数据库状态,不保护 Java 对象、缓存、文件、第三方服务等
- 应用层加锁(如
ReentrantLock)只在单 JVM 内有效,集群部署时完全失效,必须换分布式锁(如 Redis + Lua、ZooKeeper) - 混合使用时,锁粒度要对齐:DB 行锁对应「订单 ID」,应用层锁也必须用同一把 key,否则形同虚设
更隐蔽的问题:Spring 的 @Transactional 默认传播行为是 REQUIRED,嵌套方法若没声明事务,可能意外脱离锁范围。
为什么 StampedLock 很少在业务代码里真正用起来
StampedLock 支持乐观读、悲观读、写锁三种模式,理论上比 ReentrantReadWriteLock 更高效,尤其读多写少场景。但它有两个硬伤:
- 不支持重入:同一个线程重复获取读锁会阻塞,必须手动管理 stamp,极易出错
- 乐观读需二次验证:调用
validate(stamp)判断读期间是否有写操作,漏验或验错就会读到脏数据,而这个验证逻辑常被忽略或写错
一个典型错误写法:
long stamp = lock.tryOptimisticRead();
int value = x; // 假设 x 是共享变量
if (!lock.validate(stamp)) { // 这里必须重新加悲观读锁再读一次
stamp = lock.readLock();
try {
value = x;
} finally {
lock.unlockRead(stamp);
}
}实际业务中,为规避这些复杂性,多数团队直接选用更稳妥的 ReentrantReadWriteLock 或干脆用 synchronized,除非压测明确卡在读写锁争用上,且愿意投入精力做充分验证。
真正难的从来不是选哪个锁,而是厘清「谁在什么范围里改什么数据」——边界模糊时,加再 fancy 的锁也没用。










