深入解析ReentrantLock:可重入锁
目录
一、核心特性:为何需要ReentrantLock?
1. 可重入性
2. 作为synchronized的补充与增强
二、核心用法:API与最佳实践
三、公平锁 vs. 非公平锁
四、底层实现:AQS
五、synchronized和ReentrantLock的区别
在Java并发编程中,synchronized
是内置的关键字,而 ReentrantLock
则是 java.util.concurrent.locks
包提供的一个显式锁实现。它不仅是 synchronized
的强大替代品,更以其灵活性、可扩展性和丰富的功能,为开发者提供了更细粒度的并发控制手段。
一、核心特性:为何需要ReentrantLock?
1. 可重入性
与 synchronized
一样,ReentrantLock
也是可重入的。这意味着同一个线程可以多次获取同一把锁而不会产生死锁。锁内部维护了一个计数器(hold count
)来跟踪锁被重复获取的次数,线程每次获取锁时计数器加1,每次释放锁时计数器减1,只有当计数器归零时,锁才被真正释放,其他线程才能获取。
2. 作为synchronized的补充与增强
虽然 synchronized
在大多数情况下已足够优秀且简洁,但 ReentrantLock
在以下方面提供了更高级的功能:
-
-
尝试非阻塞地获取锁:
tryLock()
方法允许线程尝试获取锁,如果锁不可用则立即返回false,而不会像synchronized
那样一直阻塞。 -
可中断的锁获取:
lockInterruptibly()
方法允许在等待锁的过程中响应中断。 -
超时获取锁:
tryLock(long time, TimeUnit unit)
方法可以指定超时时间,在指定时间内获取不到锁则返回false。 -
公平性选择:构造函数允许选择创建一个公平锁(Fair Lock) 或非公平锁(Nonfair Lock)。
-
二、核心用法:API与最佳实践
ReentrantLock
的使用遵循一个明确的模式,必须显式地加锁和解锁。
Lock lock = new ReentrantLock(); // 通常为非公平锁
// Lock lock = new ReentrantLock(true); // 创建一个公平锁lock.lock(); // 手动获取锁
try {// 临界区代码:执行需要同步的操作
} finally {lock.unlock(); // 必须在finally块中手动释放锁,确保锁必然被释放,避免死锁
}
重要:必须将 unlock()
操作放在 finally
块中,以保证即使在临界区代码抛出异常的情况下,锁也能被正确释放。
三、公平锁 vs. 非公平锁
这是 ReentrantLock
提供的一个关键特性,synchronized
只提供非公平锁。
-
非公平锁(默认):当锁可用时,所有正在等待的线程都有机会抢占,与等待时间无关。这可能导致“线程饥饿”(某些线程长时间得不到锁),但吞吐量更高,因为减少了线程切换的开销。
-
公平锁:当锁可用时,它会优先分配给等待时间最长的线程。这保证了公平性,消除了饥饿,但性能开销较大,因为需要维护一个有序队列,吞吐量通常低于非公平锁。
选择策略:除非有严格的公平性要求,否则默认使用非公平锁,因为其性能优势在大多数场景下更为重要。
四、底层实现:AQS
ReentrantLock
的强大功能并非凭空实现,其核心是依赖一个叫做 AQS(AbstractQueuedSynchronizer) 的同步器框架。
AQS内部维护了一个 volatile int state(同步状态)和一个 FIFO线程等待队列(CLH变体)。
-
对于
ReentrantLock
,state
表示锁被重入的次数。为0时表示锁空闲,大于0时表示被线程持有。 -
lock()
操作本质上是通过CAS去修改state
的值。如果成功(从0改为1),则获取锁。 -
如果失败,当前线程会被封装成Node节点,加入到等待队列中并可能被挂起。
-
unlock()
操作会将state
减1。如果减为0,则唤醒等待队列中的下一个线程来尝试获取锁。
公平与非公平的实现差异就体现在 tryAcquire
方法中:公平锁在尝试获取锁前,会先检查等待队列中是否有其他线程在排队;而非公平锁则会直接尝试CAS抢占。
五、synchronized和ReentrantLock的区别
特性 | synchronized | ReentrantLock |
锁获取和锁释放 | ●隐式获取(由 JVM 自动处理,线程抢占模型) | 显式调用 lock () 和 unlock ()方法手动控制(必须在 finally 块中调用 unlock(),否则可能导致死锁) |
可中断性 | 不可中断 | 通过lockInterruptibly()方法,允许线程在等待锁时被中断 |
超时机制 | 不支持 | 通过 tryLock (timeout)方法,允许线程在指定时间内尝试获取锁 |
公平性 | 非公平 | 可选择公平或非公平 |
条件变量 | 单一 wait/notify | 支持多个 Condition 对象 |
锁状态检测 | 无法判断 | 可通过方法检测锁状态(是否被当前线程持有、是否被其他线程持有) |
锁实现机制 | Monitor监视器 | AQS同步框架 |