ReentrantLock并发安全核心是显式加锁且必须在finally中释放,优先用tryLock防阻塞,注意可重入配对、公平性选择及锁粒度控制。

使用 ReentrantLock 编写安全可靠的并发临界区代码,核心是“显式加锁 + 必须在 finally 中释放锁”,同时避免死锁、可重入误用和锁粒度不当等问题。
必须在 try-finally 中获取并释放锁
这是最易出错也最关键的一点。与 synchronized 不同,ReentrantLock 不会自动释放锁,一旦忘记 unlock 或异常提前退出,就会造成线程永久阻塞或资源泄露。
正确写法:
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 获取锁
try {
// 临界区:读写共享变量、修改集合、更新状态等
sharedCounter++;
} finally {
lock.unlock(); // 无论是否异常,都确保释放
}
错误写法(没用 finally):
lock.lock(); sharedCounter++; // 若此处抛异常,unlock 永远不会执行 lock.unlock(); // 可能被跳过
优先使用 tryLock 避免无限等待
当业务允许“拿不到锁就放弃或重试”时,用 tryLock() 更健壮,可防止因锁竞争激烈或持有者卡死导致的线程饥饿。
常见安全模式:
- 带超时的 tryLock:避免长时间阻塞
- 非阻塞 tryLock() + 业务兜底逻辑(如返回默认值、记录告警、降级处理)
- 配合循环重试(注意限制最大尝试次数,避免 CPU 空转)
示例:
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// 执行临界操作
updateCache();
} finally {
lock.unlock();
}
} else {
// 锁获取失败,走降级逻辑(如查数据库、返回旧缓存)
return fallbackData();
}
注意可重入性与锁的持有者语义
ReentrantLock 是可重入的,同一线程可多次 lock(),但必须调用相同次数 unlock()。这点常被忽略,尤其在嵌套方法调用中。
例如:
- A 方法获取锁后调用 B 方法,B 方法又尝试 lock() —— 合法,不会死锁
- 但若 A 中 lock() 一次,B 中 unlock() 一次,A 再 unlock() —— 抛
IllegalMonitorStateException - 建议:锁的获取和释放尽量放在同一作用域(如一个方法内),避免跨方法传递锁状态
根据场景选择公平锁还是非公平锁
默认构造函数创建的是非公平锁(吞吐量高,但可能饿死某些线程);传入 true 创建公平锁(按 FIFO 排队,响应更可预测,但性能略低)。
一般建议:
- 高并发、对延迟敏感的场景(如高频计数器、缓存更新)用非公平锁(默认)
- 对响应时间一致性要求高、且线程竞争不极端的场景(如后台任务调度)可考虑公平锁
- 不要仅凭“公平听起来更好”就启用公平模式,需实测 QPS 和长尾延迟
不复杂但容易忽略:锁对象生命周期要稳定,别在循环里反复 new Lock;多个相关临界资源尽量共用一把锁,避免交叉加锁引发死锁;必要时用 lock.getHoldCount() 或 isHeldByCurrentThread() 辅助调试。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











