ReentrantLock 使用需严守四边界:加锁必须在 try 块内;unlock() 必须且仅在 finally 中调用一次;默认非公平,公平需显式指定;优先用 tryLock 或 lockInterruptibly 避免死等。

ReentrantLock 灵活但易出错,用错一两行就可能引发死锁、锁泄漏或线程饥饿。关键不是记住所有 API,而是守住几个不可妥协的操作边界。
加锁必须写在 try 块里,不能放外面
常见错误是把 lock.lock() 放在 try 外面:
- 如果 lock() 自身抛异常(比如被中断),finally 不会执行,unlock() 就永远不调用
- 正确写法:把 lock() 和业务逻辑一起包进 try,确保无论哪一步出错,finally 都能释放锁
unlock() 必须放在 finally 中,且只能调用一次对应 lock()
ReentrantLock 不像 synchronized 那样自动释放,它靠计数器管理重入次数:
- 每调用一次 lock(),内部计数 +1;每调用一次 unlock(),计数 -1
- 只有计数归零,锁才真正释放。多 unlock() 会抛 IllegalMonitorStateException
- 少 unlock() 会导致其他线程永远等不到锁,形成“假死”
别默认以为它是公平锁,非公平才是常态
new ReentrantLock() 创建的是非公平锁,这是设计选择,不是 bug:
立即学习“Java免费学习笔记(深入)”;
- 新线程可能插队抢到刚释放的锁,导致排队久的线程一直得不到执行(饥饿)
- 如需严格顺序,显式传参:new ReentrantLock(true)
- 但要注意:公平锁吞吐量通常低 10%–30%,只在业务逻辑明确要求“先来先得”时启用
用超时或可中断获取锁,别死等
lock() 是无限阻塞的,一旦发生锁竞争+异常路径,排查极其困难:
- 优先考虑 tryLock(long, TimeUnit):设 3–5 秒超时,超时后记录日志、降级或重试
- 对取消敏感的场景(如任务中断、服务关闭),改用 lockInterruptibly()
- 避免在持有锁期间调用可能阻塞的外部服务(如 RPC、DB 查询),否则整个锁等待链会被拖长


















