ReentrantLock
是 Java 提供的一个显式锁实现,位于 java.util.concurrent.locks
包中。
它提供了与 synchronized
类似的同步功能,但相比 synchronized
,它更加灵活,并提供了更多的特性,如公平性选择、尝试获取锁、可中断获取锁等。
实现原理
1. 基于 AQS(AbstractQueuedSynchronizer)
ReentrantLock
的核心实现依赖于 AbstractQueuedSynchronizer
(简称 AQS),这是一个用于构建锁和其他同步组件的基础框架。AQS 使用了一个 FIFO 队列来管理等待获取锁的线程,通过一个原子变量表示当前锁的状态。
- 状态管理:在
ReentrantLock
中,AQS 的状态(state)用来表示锁的持有情况。如果 state 为 0,则表示锁未被任何线程持有;如果 state 大于 0,则表示锁已经被某个线程持有,并且数值代表持有该锁的重入次数。 - 队列机制:当一个线程尝试获取锁失败时,它会被封装成一个节点加入到 AQS 维护的同步队列中等待。只有当它前面的所有节点都已从队列中移除(即获得了锁并释放了锁),该节点对应的线程才能尝试获取锁。
2. 公平锁 vs 非公平锁
ReentrantLock
支持两种类型的锁:公平锁和非公平锁,默认情况下是使用非公平锁。
- 公平锁:遵循先来先服务的原则,即等待时间最长的线程将最先获得锁。这可以避免“饥饿”现象,但可能会导致性能下降,因为每次都需要检查队列头部是否有其他线程等待。
- 非公平锁:允许插队,即新来的线程有机会抢占锁,即使已经有其他线程在等待。这种方式通常能提供更好的吞吐量,但在某些情况下可能导致长时间等待的线程始终得不到执行机会。
3. 锁的获取与释放
- 获取锁:
- 调用
lock()
方法尝试获取锁。如果是非公平锁,会首先尝试CAS操作直接获取锁,成功则立即返回;如果不成功,则进入同步队列等待。 - 对于公平锁,会直接进入同步队列排队等待,直到轮到自己获取锁。
- 调用
- 释放锁:
- 调用
unlock()
方法释放锁。每次调用都会减少锁的重入计数,当计数归零时,锁才真正被释放,并唤醒同步队列中的下一个节点对应的线程。
- 调用
4. 其他特性
- 可中断获取锁:通过
lockInterruptibly()
方法可以在获取锁的过程中响应中断请求,这对于需要取消长时间等待的操作非常有用。 - 尝试获取锁:
tryLock()
方法允许以非阻塞的方式尝试获取锁,如果此时无法获取锁,则立即返回而不是等待。还可以指定超时时间,如tryLock(long timeout, TimeUnit unit)
。 - 条件变量:
ReentrantLock
提供了newCondition()
方法创建条件变量(Condition),使得线程可以在满足特定条件之前暂停执行,类似于传统的监视器对象上的wait/notify
机制。
综上所述,ReentrantLock
通过 AQS 实现了一套高效且灵活的锁机制,不仅支持基本的同步需求,还提供了多种高级功能,使其成为处理复杂并发场景的强大工具。
THE END