ReentrantLock支持可中断等待、超时获取、公平锁、多条件队列及细粒度锁控制,而synchronized仅支持隐式加锁且不可中断、无超时、无法分离加锁与释放逻辑。

ReentrantLock 能做什么 synchronized 做不了
最直接的答案:它支持可中断等待、超时获取、公平锁策略、多条件队列,以及更细粒度的锁控制。而 synchronized 只能隐式加锁、不可中断、不支持超时、无法分离「加锁」和「释放」逻辑——比如你想在 try 块外决定是否释放锁,synchronized 根本做不到。
典型场景包括:等待数据库连接池资源时不想无限阻塞、异步任务中需响应线程中断、多个业务逻辑共享同一把锁但需要不同唤醒条件。
必须手动配对 lock() 和 unlock()
这是最容易出错的地方。ReentrantLock 不像 synchronized 那样自动释放,一旦忘记在 finally 中调用 unlock(),就会导致死锁或资源饥饿。
- 正确写法一定是:
lock.lock()在 try 前,lock.unlock()必须放在 finally 块里 - 不能把
unlock()放在 try 内部——异常抛出会跳过它 - 不能依赖作用域自动清理,Java 没有析构函数机制
- 如果锁是成员变量,还要注意 this 引用逸出风险(比如构造函数里就启动线程去 lock)
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock(); // 缺少这行,锁永远不释放
}
tryLock(long, TimeUnit) 是防卡死的关键
当业务不能接受无限等待时,tryLock() 提供了可控的获取方式。它返回 boolean,成功才继续,失败可降级、重试或快速返回。
常见误用是只调用无参 tryLock()(立即返回),但其实带超时的版本才真正解决阻塞问题。
- 超时单位必须用
TimeUnit枚举,不能传毫秒整数 - 超时后返回 false,不代表锁被其他线程持有——也可能是当前线程已持有该锁(可重入)
- 如果在超时时间内被中断,会抛
InterruptedException,需捕获并处理 - 性能上比
lock()略低,但换来的是确定性,值得
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 执行临界区
} finally {
lock.unlock();
}
} else {
// 处理获取失败,比如返回错误码或走缓存
}
公平锁不是默认选项,且代价明显
新建 ReentrantLock() 默认是非公平的,想开启公平模式必须显式传 true:new ReentrantLock(true)。但别急着开——公平锁会显著降低吞吐量。
原因在于:非公平模式允许刚释放锁的线程“插队”再次获取,减少上下文切换;而公平模式强制排队,所有线程都得走 CAS + 队列唤醒路径。
- 仅在明确观察到线程饥饿(某些线程长期拿不到锁)时才考虑公平模式
- 高并发写场景下,公平锁吞吐可能下降 30% 以上
- 公平性只影响等待线程的获取顺序,不影响可重入性或锁降级等行为
另外,isFair() 方法只能查状态,不能运行时切换。










