在Java中,AQS(AbstractQueuedSynchronizer,抽象队列同步器)通过设计一个独占和共享的同步机制,提供了可重入锁的实现。AQS 的可重入性主要依赖于它对线程状态的跟踪。具体来说,可重入性是指同一个线程在获得锁之后可以多次进入(加锁多次),而不引发死锁。这是通过一个“重入计数器”来实现的。
下面是AQS实现可重入性的核心机制:
1. 线程持有状态(state)
AQS 使用一个 state
变量来表示锁的持有状态。在独占锁(如 ReentrantLock
)的情况下,state
变量记录锁被持有的次数。AQS 的设计允许同一个线程多次获取锁,每次获取锁时,state
变量会递增,而每次释放锁时,state
变量会递减,直到 state
变为 0 时,锁才会真正释放。
2. 当前持有锁的线程
AQS 通过内部的一个线程引用 exclusiveOwnerThread
来跟踪当前持有锁的线程。当一个线程尝试获取锁时,AQS 会检查当前线程是否已经持有锁(即 exclusiveOwnerThread == currentThread
)。如果是同一个线程,则允许该线程再次获取锁,表示“可重入”。
3. 可重入的判断过程
- 当一个线程第一次获取锁时,AQS 会将
exclusiveOwnerThread
设置为该线程,并将state
从 0 设置为 1。 - 如果同一线程再次尝试获取锁,AQS 看到
exclusiveOwnerThread
已经是当前线程,于是允许锁的重入,并将state
递增。 - 当线程释放锁时,AQS 会减少
state
的值。只有当state
减为 0 时,AQS 才会将exclusiveOwnerThread
置为 null,表示锁已完全释放。
4. 代码示例
以 ReentrantLock
为例,ReentrantLock
是基于 AQS 实现的可重入锁:
class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
lock.lock(); // 第一次加锁
try {
// 执行任务
anotherMethod(); // 同一线程可以再次加锁
} finally {
lock.unlock(); // 第一次解锁
}
}
public void anotherMethod() {
lock.lock(); // 第二次加锁
try {
// 执行其他任务
} finally {
lock.unlock(); // 第二次解锁
}
}
}
在这个例子中,performTask
和 anotherMethod
都会加锁,而由于是同一线程,所以 lock
会被允许多次加锁。
5. 锁的释放
当线程多次加锁时,每次加锁都对应一次释放。只有当释放次数与加锁次数相等时,锁才会真正释放,允许其他线程获取。
总结
AQS 的可重入性主要是通过 state
变量和 exclusiveOwnerThread
来实现的。它通过跟踪线程加锁的次数以及锁的当前拥有者,确保同一个线程可以多次进入锁区域,而不会导致死锁。这是 Java 中许多同步类(如 ReentrantLock
)的基础。