优先用对象锁代替类锁、拆分临界区、避免同步块内调用外部方法;高争用时用ReentrantLock替代synchronized;读多写少用ReentrantReadWriteLock或StampedLock;锁对象应为私有final实例字段,避免字符串或小整数包装类。

锁粒度太大导致线程频繁阻塞怎么办
Java里用 synchronized 修饰整个方法或大段代码块,等同于把“整张表”锁住,哪怕两个线程只操作不同行数据,也得排队。典型表现是:CPU利用率不高,但 Thread.State: BLOCKED 线程数持续上升,jstack 能看到一堆线程卡在同一个锁对象上。
- 优先用对象锁代替类锁:
synchronized(this)或synchronized(instanceField),避免synchronized(ClassName.class) - 拆分临界区:把不共享的操作移出同步块,只包裹真正需要互斥的语句
- 避免在同步块内调用外部方法(尤其是可能阻塞或回调的),防止锁持有时间被意外拉长
什么时候该用 ReentrantLock 替代 synchronized
synchronized 自动加锁/释放,但没法控制锁等待策略;而 ReentrantLock 支持超时获取(tryLock(long, TimeUnit))、可中断等待(lockInterruptibly())、公平性选择,更适合高竞争场景下的细粒度控制。
- 高争用下,
ReentrantLock的自旋+队列机制比synchronized的操作系统级阻塞更轻量 - 必须配对使用
lock()和unlock(),且unlock()务必放在finally块中,否则极易发生死锁 - 不要在
ReentrantLock的临界区内再嵌套synchronized,JVM 不保证两者之间的可见性或顺序一致性
读多写少场景下怎么避免读操作也被锁住
如果多个线程频繁读、极少写,用独占锁会让所有读请求串行化,白白浪费并发能力。这时应转向读写分离模型。
-
ReentrantReadWriteLock是标准解法:读锁可重入且共享,写锁独占;但要注意“写饥饿”问题——持续读请求会阻塞写线程 - 若读操作本身是无状态、无副作用的纯计算(如查缓存、解析 JSON),考虑用
volatile+ CAS(AtomicInteger、AtomicReference)替代锁 -
StampedLock更激进:乐观读(tryOptimisticRead)不加锁,仅在验证失败后退回到悲观读,适合读远多于写的低延迟场景;但不支持重入,且 stamp 验证逻辑容易写错
锁对象本身引发的隐式竞争
看似分散的锁,可能因哈希码冲突或对象复用,实际落在同一个锁桶(如 ConcurrentHashMap 的 segment 或 Java 8+ 的 Node 数组槽位)上,造成“伪竞争”。
立即学习“Java免费学习笔记(深入)”;
- 避免用常量字符串或小整数包装类(如
"key"、Integer.valueOf(100))作锁对象——它们可能被 JVM 缓存并复用 - 锁对象最好为私有 final 实例字段,确保生命周期与业务对象一致,且不被外部引用干扰
- 用
new Object()创建锁对象虽简单,但需确认不会无意中泄漏引用,否则 GC 无法回收,间接导致锁长期驻留
jcmd VM.native_memory 或 JFR 录制锁事件,比凭经验猜更可靠。









