ReentrantLock实现可重入性的关键在于其内部维护了一个计数器(通常称为state),用于记录当前线程获取锁的次数。以下是ReentrantLock实现可重入性的详细解释:
一、计数器的作用
- 当一个线程首次获取锁时,计数器被初始化为1,表示该线程已经获取了一次锁。
- 如果同一个线程再次请求锁(即重入),计数器会递增,以记录该线程获取锁的次数。
- 当线程释放锁时,计数器会递减,直到计数器降为0时,锁才被完全释放,此时其他线程才能获取该锁。
二、实现原理
ReentrantLock的可重入性是通过其内部的同步器(Sync)实现的,Sync是一个继承自AbstractQueuedSynchronizer(AQS)的抽象类。ReentrantLock提供了两种同步器实现:公平同步器(FairSync)和非公平同步器(NonfairSync)。
-
非公平同步器(NonfairSync):
- 默认情况下,ReentrantLock使用非公平同步器。
- 在尝试获取锁时,如果锁已被其他线程持有,非公平同步器会检查当前尝试获取锁的线程是否是当前持有锁的线程。如果是,说明发生了重入,此时会将计数器加1,并返回true表示获取锁成功。
- 在释放锁时,非公平同步器会递减计数器,并检查计数器是否已降为0。如果是,表示锁已被完全释放,此时会将持有锁的线程设置为null,以便其他线程可以获取锁。
-
公平同步器(FairSync):
- 公平同步器的实现与非公平同步器类似,但在获取锁时采用了不同的策略。
- 它会按照线程请求锁的顺序来分配锁,即先请求的线程会先获得锁。这有助于减少线程的饥饿问题,但可能会降低吞吐量。
三、示例代码
以下是一个简单的Java代码示例,演示了ReentrantLock的可重入性:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private ReentrantLock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 获取锁
try {
count++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
increment(); // 递归调用,测试ReentrantLock的可重入性
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) {
final ReentrantLockDemo demo = new ReentrantLockDemo();
// 创建两个线程,同时调用increment()方法
new Thread(new Runnable() {
public void run() {
demo.increment();
}
}).start();
new Thread(new Runnable() {
public void run() {
demo.increment();
}
}).start();
}
}
在这个示例中,我们创建了一个ReentrantLock对象来保护共享资源count
,并在increment()
方法中获取锁。然后,我们递归调用increment()
方法来测试ReentrantLock的可重入性。运行程序后,你会发现两个线程会不断递归调用increment()
方法,每个线程会自己维护一个计数器(即ReentrantLock内部的计数器),这就是ReentrantLock的可重入性的体现。
综上所述,ReentrantLock通过内部维护一个计数器来实现可重入性。当一个线程多次获取锁时,计数器会递增;当线程释放锁时,计数器会递减。直到计数器降为0时,锁才被完全释放。这种机制使得同一个线程可以多次获取同一个锁而不会导致死锁。