Skip to content

如何优化 Java 中的锁的使用? ​

  • 标签:Java并发、Java

🏛 业务架构师 ​

锁优化我按投入产出比从高到低排:

1. 缩小临界区(零成本,先做)

锁内只放必须互斥的代码——IO、远程调用、复杂计算全部挪出去。临界区缩短一半,吞吐接近翻倍,而这是纯代码重构,没有引入任何新复杂度。

2. 减小锁粒度(架构动作)

  • 按业务对象加锁:锁 orderId:1001 而不是全局锁——不同订单天然并行,全局锁是"用一列火车的运力跑全国货运";
  • 分段锁:热点库存拆 10 个子段(ConcurrentHashMap 的 segment 思想),冲突概率降一个数量级;
  • 读写分离:读多写少用 ReadWriteLock/StampedLock——配置表、字典缓存的标配。

3. 无锁化(用结构换锁)

  • CAS/Atomic 适合低冲突;LongAdder 分段计数处理热点计数;
  • 不可变对象 + volatile 引用替换:配置快照原子切换,读侧永远无锁;
  • 本地缓存挡读 + 异步合并写:热点账户每秒 8 万次更新,加什么锁都白搭——写请求攒批合并落库(合并 100 次余额变动为一次 update),把锁问题变成吞吐问题。

4. 队列串行化(消灭锁)

单线程 EventLoop 消费队列(Disruptor/内存队列)——同一账户的操作路由到同一线程,串行执行无锁竞争。交易撮合、账户流水这类场景的最优解。

纪律三条:先测量再优化(profile 确认锁竞争真是瓶颈,多数"锁慢"其实是锁内 IO 慢);死锁三件套(排序加锁、tryLock 超时、小事务)配套;每次优化后压测验证——锁优化很容易引入新 bug,收益要覆盖风险。

一句话:锁优化的阶梯是缩范围→降粒度→去锁化→串行化,从免费的开始买。

🏗 系统架构师 ​

锁优化我的方法论是"能不共享就不共享,能少锁就少锁,能细粒度就细粒度,能无锁就无锁":

  • 缩小临界区:锁内只放状态变更,IO/RPC/日志移出锁外(先无锁算好,锁内提交);
  • 减少锁持有时间:大对象构建放锁外(局部变量构造完再引用替换);
  • 锁粒度细分:全局锁 → 分段锁。ConcurrentHashMap 1.7 的 Segment、LongAdder 的 Cell、按业务 ID 分桶加锁(一把锁拆 16 把,冲突率指数下降);
  • 读写分离:读多写少用 ReadWriteLock,读竞争也大用 StampedLock 乐观读(读时不加锁,读后验证版本,失败升级悲观读);或不可变快照 + volatile 引用整体替换——读永远无锁;
  • 无锁化:CAS/Atomic 系列适合低冲突计数与状态翻转;ThreadLocal 消除共享(SimpleDateFormat 的经典改造:包装进 ThreadLocal 或换 DateTimeFormatter);
  • 锁合并/粗化的反向权衡:循环内反复加锁,适度粗化一次锁反而省同步开销(JVM 也会做锁粗化);
  • 并发容器替代手写:BlockingQueue、ConcurrentHashMap 替代 Collections.synchronizedXxx;
  • 控制锁顺序:多锁按序获取防死锁,配 tryLock 超时。

心法:锁是兜底手段不是默认手段——先问"这个状态能不能不共享(任务本地化、消息化)",再问"能不能用不可变",最后才轮到锁。

💻 全栈开发专家 ​

我的锁优化清单,按收益排序:

1. 缩小临界区(最常见、收益最大)

java
// 坏:把整个方法锁住,IO 也在锁内
public synchronized void process() { queryRpc(); transform(); save(); }
// 好:只锁共享数据操作,重活挪出去
public void process() {
    Data d = queryRpc();                      // 锁外
    Result r = transform(d);                  // 锁外
    synchronized (this) { save(r); }          // 只锁必要段
}

2. 减小锁粒度:分段锁思想(ConcurrentHashMap 1.7)、LongAdder 分散计数热点——热点计数器从 synchronized 改 LongAdder 我实测 5 倍吞吐提升。

3. 读写分离:读多写少用 ReadWriteLock 或 StampedLock(乐观读,性能更好)。

4. 无锁化:CAS/Atomic、不可变对象(final 全字段 + 副本更新)、ThreadLocal 隔离(变量不上共享就没竞争)。

5. JVM 层(感知即可):锁消除(逃逸分析后局部对象上的锁被去掉)、锁粗化(连续小锁合并成大锁减少开销)。

6. 避免锁内做不可控的事:锁内调 RPC/DB/IO 是死锁和长尾的头号来源;tryLock 超时兜底防死锁。

排查手段:jstack 看 BLOCKED 集中在哪个锁;Arthas thread -b 找阻塞源头;压测对比锁竞争指标(contended lock monitors)。优化前先测量,别凭感觉加锁减锁。