前言在多线程编程中,一个线程可能会在持有某个锁的情况下,再次尝试获取同一个锁。如果锁不可重入,那这种嵌套锁的场景会导致线程死锁。
试想一下。如果A线程来调用一个递归函数,函数是需要锁的,当A线程获取锁后,再次递归调用方法时,发现已经有线程获取锁了,并且这个线程就是A线程自己。这种情况下应该怎么办?
所以,在JAVA中,为了解决这种问题,增加了可重入锁,可重入锁的的核心作用就是防止死锁。
什么是可重入锁?可重入锁(ReentrantLock)是指应用中同一个线程在多层函数都有锁的情况下,如果在外层函数获得锁 ,那么对内层函数的调用 仍然能获取该锁的代码。也就是说同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
简单点说,一个线程拥有了开门的钥匙后,那他就可以一直进入这个家里了。
需要注意的是。可重入锁必须确保锁在finally中释放,以避免因异常导致锁未释放,从而引发死锁。
我们看一下下面的代码:
在下面的测试代码中,如果 lock 是不可重入的,当 outerMethod 调用innerMethod 时,innerMethod 会尝试再次获取锁,但此时锁已经被 outerMethod 持有,导致线程进入死锁状态。
代码语言:txt复制package com.demo;
import java.util.concurrent.locks.ReentrantLock;
public class T7 {
private final ReentrantLock lock = new ReentrantLock();
public void outerMethod() {
lock.lock();
try {
System.out.println("Outer method is running.");
innerMethod();
} finally {
lock.unlock();
}
}
public void innerMethod() {
lock.lock();
try {
System.out.println("Inner method is running.");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
T7 demo = new T7();
// 同一个线程调用 outerMethod 和 innerMethod
demo.outerMethod();
}
}打印结果:
由打印结果可以直观的确定可重入锁的意义。
可重入锁原理当线程尝试获取锁时,如果锁已被占用,则判断占用锁的是否是当前线程,
如果是当前线程执有锁,则可以再次获取锁,通过维护一个线程独有的锁计数器来实现。每次获取锁时计数器加1,释放锁时减1,只有当计数器为0时,锁才真正释放。
如果不是当前线程执有锁,则线程会被加入等待队列并阻塞;当锁释放时,队列中的线程被唤醒尝试获取锁。
代码语言:txt复制final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前锁的状态(state),state 表示当前锁的持有次数
int c = getState();
// 如果当前锁的状态为 0,表示锁未被任何线程持有
if (c == 0) {
// 使用 CAS 操作尝试将锁的状态从 0 设置为 acquires(通常是 1)
if (compareAndSetState(0, acquires)) {
// 如果 CAS 成功,设置当前线程为锁的持有者
setExclusiveOwnerThread(current);
// 返回 true 表示获取锁成功
return true;
}
}
// 如果当前锁已被持有,并且持有者是当前线程(可重入锁的特性)
else if (current == getExclusiveOwnerThread()) {
// 计算新的锁状态值(当前状态 + acquires)
int nextc = c + acquires;
// 检查是否发生溢出(锁的最大持有次数超过 Integer.MAX_VALUE)
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 更新锁的状态为新的值
setState(nextc);
// 返回 true 表示获取锁成功
return true;
}
// 如果当前线程无法获取锁(锁被其他线程持有,且当前线程不是持有者)
return false;
}可重试锁从最开始获取锁到时最后获取到锁的原理图如下:
总结可重入锁的出现主要是为了解决嵌套锁导致的死锁问题。可重入锁这种解决死锁的方式,使得我们能够在复杂的并发编程中更加安全和高效地使用锁,而不用考虑其他问题。