Java 锁相关【三、ReentrantLock 与显式锁机制全解析】
ReentrantLock 与显式锁机制全解析
在 Java 并发编程中,除了 synchronized 提供的内置锁外,java.util.concurrent.locks 包下的 显式锁(Explicit Lock) 机制为开发者提供了更强大、更灵活的控制手段。其中最核心的实现就是 ReentrantLock。
本文将从 基本概念 → 内部实现 → 与 synchronized 的比较 → 高级特性 → 工程实践 → 优缺点分析 等角度,对 ReentrantLock 做全面解析。
一、显式锁与内置锁的区别
synchronized 是 隐式锁,编译器和 JVM 自动管理锁的获取与释放;而 ReentrantLock 是 显式锁,需要开发者手动获取与释放。
1.1 使用方式
-
synchronized:
synchronized (lock) { // 临界区 } -
ReentrantLock:
ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // 临界区 } finally { lock.unlock(); }
1.2 差异对比
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 获取与释放方式 | 自动(编译器生成字节码) | 手动调用 lock()/unlock() |
| 公平性选择 | 不支持 | 支持公平锁与非公平锁 |
| 可中断性 | 不支持 | lockInterruptibly() 可中断等待 |
| 尝试获取锁 | 不支持 | tryLock() 可立即或超时尝试 |
| 条件变量(Condition) | 通过 wait/notify | 多个 Condition,更灵活 |
| 可重入性 | 支持 | 支持 |
| 性能(JDK 1.6+ 后) | 已优化,性能接近 ReentrantLock | 高并发竞争下略优 |
二、ReentrantLock 的基本原理
2.1 可重入性
“可重入”指同一线程可以重复获取同一把锁,而不会被阻塞。
lock.lock();
try {
lock.lock(); // 可重入,不会死锁
try {
// 临界区
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
内部通过 计数器(state) 记录加锁次数,释放时逐步递减,直到计数为 0 才真正释放锁。
2.2 内部实现
ReentrantLock 基于 AQS(AbstractQueuedSynchronizer) 实现。
AQS 核心思想
- 使用 volatile int state 表示锁的状态(0=未锁定,>0=锁重入次数)。
- 使用 FIFO 等待队列(CLH 队列) 管理未获取到锁的线程。
- 使用 CAS 操作 实现原子性的 state 修改。
简化流程:
2.3 公平锁与非公平锁
-
非公平锁(默认)
- 线程尝试直接 CAS 获取锁,不成功才进入队列。
- 吞吐量高,但可能导致“饥饿”。
-
公平锁
- 按照等待时间顺序严格排队,先来先得。
- 吞吐量较低,但保证公平性。
创建方式:
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
ReentrantLock unfairLock = new ReentrantLock(); // 默认非公平锁
2.4 可中断锁
与 synchronized 不同,ReentrantLock 支持 响应中断:
try {
lock.lockInterruptibly();
try {
// 临界区
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
// 处理中断逻辑
}
这在死锁恢复或任务取消场景下非常有用。
2.5 tryLock 与超时获取
-
立即尝试获取
if (lock.tryLock()) { try { // 临界区 } finally { lock.unlock(); } } -
带超时的获取
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) { try { // 临界区 } finally { lock.unlock(); } } else { // 超时未获取到锁 }
这种能力在高并发业务中非常实用,避免线程长时间阻塞。
2.6 条件变量(Condition)
相比 synchronized 只能依赖 wait/notify,ReentrantLock 提供了更灵活的 Condition:
Condition condition = lock.newCondition();
lock.lock();
try {
while (!ready) {
condition.await(); // 进入等待队列
}
// 执行业务
condition.signal(); // 唤醒一个等待线程
} finally {
lock.unlock();
}
- 一个锁可以关联 多个 Condition 对象,避免单一
waitSet的局限。 - 更利于复杂场景下的线程协调。
三、典型应用场景
-
需要超时获取锁的业务逻辑
- 例如分布式环境下,避免无限等待资源。
-
响应中断的锁等待
- 可用于任务取消、线程池关闭时的安全退出。
-
精细化线程协作
- 使用多个 Condition,代替
synchronized + wait/notify,提升代码可读性。
- 使用多个 Condition,代替
-
高并发竞争下
- 非公平锁模式下吞吐量优于
synchronized。
- 非公平锁模式下吞吐量优于
四、使用建议
-
优先使用 synchronized
- 在语义清晰、功能足够时,推荐首选
synchronized(性能已不再是短板)。
- 在语义清晰、功能足够时,推荐首选
-
在以下情况使用 ReentrantLock
- 需要 公平性保证;
- 需要 可中断锁获取;
- 需要 超时锁获取;
- 需要 多个条件变量;
- 需要更复杂的同步控制。
-
注意 unlock() 必须写在 finally 块中,否则可能造成锁泄漏。
五、总结
- ReentrantLock 是基于 AQS 的显式锁,实现了 可重入、公平/非公平选择、可中断、tryLock、条件变量 等高级功能。
- 与
synchronized相比,它提供了更多的灵活性和可控性,但需要开发者谨慎管理锁的获取与释放。 - 在工程实践中,推荐以 synchronized 为首选,仅在业务需要时引入 ReentrantLock。
一句话总结:
synchronized语义清晰、简单易用,ReentrantLock 功能丰富、灵活可控。选择哪一种,取决于业务复杂度与工程场景。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120848

浙公网安备 33010602011771号