内容摘要
可重入锁有助于避免死锁,因为它允许线程在不释放已持有的锁的情况下,重新进入同步代码块,在某些情况下是非常必要的,
什么是可重入性?
锁的可重入性(Reentrant)是指同一个线程可以多次获取同一个锁,而不会导致死锁或其他线程无法获取该锁的情况,可重入锁是一种特殊的锁,它允许一个线程在已经持有该锁的情况下,再次获取(或重入)该锁,而不会产生冲突或死锁。
这种机制是通过为每个锁关联一个持有者和一个计数器来实现的,当线程首次获取锁时,它成为锁的持有者,并且计数器设置为1。如果同一个线程再次获取该锁,计数器就会增加,每次线程释放锁时,计数器都会减少,只有当计数器归零时,其他线程才有机会获取该锁。
可重入锁有助于避免死锁,因为它允许线程在不释放已持有的锁的情况下,重新进入同步代码块,在某些情况下是非常必要的,例如,在递归函数中,或者在需要调用其他也使用相同锁的方法时。
Java中的ReentrantLock类就是一个可重入锁的实现,此外,synchronized关键字在Java中提供的内置锁也是可重入的。
代码案例
下面是一个简单的代码示例,演示了ReentrantLock可重入锁的特性。
这个示例包括一个Counter类,它使用ReentrantLock来保护对内部计数器的访问,以及一个客户端类Client,它调用Counter的方法来增加计数器的值,如下代码:
import java.util.concurrent.locks.ReentrantLock;
/**
* @创建人 程序员古德 <br>
* @创建时间 2024/1/18 23:51 <br>
* @修改人 暂无 <br>
* @修改时间 暂无 <br>
* @版本历史 暂无 <br>
*/
// Counter类使用ReentrantLock来保护计数器的状态
public class Counter {
private final ReentrantLock lock = new ReentrantLock();
private int count = 0;
// 增加计数器的值
public void increment() {
lock.lock(); // 获取锁
try {
count++;
System.out.println("Counter incremented by " + Thread.currentThread().getName() + " to " + count);
} finally {
lock.unlock(); // 释放锁
}
}
// 递归增加计数器的值,演示可重入锁的特性
public void recursiveIncrement(int depth) {
if (depth <= 0) {
return;
}
lock.lock(); // 在递归调用中重复获取锁
try {
count++;
System.out.println("Counter recursively incremented by " + Thread.currentThread().getName() + " to " + count + " at depth " + depth);
recursiveIncrement(depth - 1); // 递归调用
} finally {
lock.unlock(); // 递归返回时释放锁
}
}
}
// 客户端类,用于调用Counter的方法
public class Client implements Runnable {
private final Counter counter;
public Client(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
// 调用increment方法
counter.increment();
// 调用recursiveIncrement方法进行递归增加
counter.recursiveIncrement(3);
}
public static void main(String[] args) {
Counter counter = new Counter();
// 创建并启动两个客户端线程
Thread clientThread1 = new Thread(new Client(counter));
Thread clientThread2 = new Thread(new Client(counter));
clientThread1.start();
clientThread2.start();
// 注意:由于线程调度的不确定性,输出的顺序可能会有所不同
}
}
在上面代码中,Counter类有一个受ReentrantLock保护的count变量,increment方法简单地增加计数器的值,而recursiveIncrement方法递归地增加计数器的值,演示了同一个线程可以多次获取同一个锁而不会导致死锁的情况。程序运行结果如下:
Counter incremented by Thread-0 to 1
Counter recursively incremented by Thread-0 to 2 at depth 3
Counter recursively incremented by Thread-0 to 3 at depth 2
Counter recursively incremented by Thread-0 to 4 at depth 1
Counter incremented by Thread-1 to 5
Counter recursively incremented by Thread-1 to 6 at depth 3
Counter recursively incremented by Thread-1 to 7 at depth 2
Counter recursively incremented by Thread-1 to 8 at depth 1
这个输出显示了两个线程交替增加计数器的值,并且每个线程都能够递归地获取锁来增加计数器的值,而不会相互阻塞或导致死锁,这就是ReentrantLock可重入性的体现。
实现原理
ReentrantLock的可重入性是通过其内部类Sync实现的,该类继承自AbstractQueuedSynchronizer(AQS),Sync有两个子类:NonfairSync和FairSync,分别表示非公平锁和公平锁,但它们在可重入性的实现上是相同的,可重入性的关键在于,当一个线程尝试获取锁时,如果锁已经被同一个线程持有,那么该线程可以再次获取锁而不会阻塞,这是通过在AQS中维护一个状态变量state和一个表示当前锁持有者的线程变量来实现的。
以下是ReentrantLock中与可重入性相关的关键代码段及其解释:
1、AQS的状态变量state:在AQS中,state变量用于表示同步状态,对于ReentrantLock,这个变量表示当前线程持有锁的重入次数。
2、Sync.tryAcquire(int acquires)方法:当线程尝试获取锁时,会调用此方法,它首先检查锁是否已经被当前线程持有,如果是,则增加重入计数,如果不是,则尝试获取锁。
如下代码案例:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
代码解释:
- 如果state为0,表示锁未被持有,尝试通过CAS操作将其设置为acquires(通常为1),并将当前线程设置为锁的独占所有者。
- 如果当前线程已经是锁的独占所有者,则增加state的值,表示重入次数增加。
- 如果其他线程尝试获取锁,则返回false。
3、Sync.tryRelease(int releases)方法: 当线程释放锁时,会调用此方法,它减少重入计数,并在计数为0时释放锁,如下代码:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
代码解释:
- 从state中减去releases(通常为1),表示减少重入次数。
- 检查当前线程是否是锁的独占所有者,如果不是,则抛出异常。
- 如果重入次数减至0,将锁的独占所有者设置为null,并标记free为true表示锁已被完全释放。
- 更新state的值。