重入锁(Reentrant Lock)简介
定义
重入锁(Reentrant Lock),也称为递归锁,是一种允许同一个线程多次获取同一把锁的机制。在多线程编程中,锁是确保共享资源被安全访问的关键工具。当一个线程需要进入由锁保护的代码块时,它必须首先获得这把锁;如果锁已经被其他线程持有,则该线程会被阻塞直到能够获取到锁。
对于非重入锁,一旦某个线程获得了锁,在释放这个锁之前,如果再次尝试获取相同的锁,那么它将被永久阻塞,导致死锁。而重入锁则解决了这个问题,它允许已经持有一个锁的线程重复获取该锁而不会被阻塞。这种能力是通过为每个锁维护一个与之关联的计数器来实现的。当一个线程第一次获取锁时,计数器设置为1;每当同一线程再次获取该锁时,计数器递增;当线程释放锁时,计数器递减,只有当计数器达到0时,锁才会被完全释放,使得其他等待的线程有机会获取锁。
应用场景
重入锁的一个典型应用场景是在一个类的方法调用链中,其中一个方法调用了另一个方法,而这两个方法都需要对同一个共享资源进行同步操作。例如,父类中的一个同步方法可能被子类重写,并且子类的方法中又调用了父类的方法。在这种情况下,如果没有重入锁,子类的方法会因为无法再次获取已被自己持有的锁而陷入死锁状态。有了重入锁,同一线程可以安全地多次进入临界区而不会造成死锁。
Java 中的实现
Java 中提供了多种实现重入锁的方式,包括内置的 synchronized
关键字以及 java.util.concurrent.locks.ReentrantLock
类。ReentrantLock
是从 JDK 1.5 开始引入的一种灵活度更高的锁实现,它可以替代 synchronized
并提供额外的功能,如公平锁、条件变量和定时锁定等。此外,ReentrantLock
需要显式地调用 lock()
和 unlock()
方法来进行加锁和解锁,这也意味着开发人员需要更加小心地管理锁的状态以避免资源泄漏。
总结来说,重入锁的主要功能是避免线程死锁的问题,同时它也为复杂的并发控制提供了更大的灵活性。在设计多线程应用程序时,正确理解和使用重入锁可以帮助开发者编写出更加健壮和高效的代码。
tryAcquire 方法(加锁方法的源代码)
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread(); // 获取当前线程
int c = getState(); // 获取锁的状态
if (c == 0) { // 检查锁是否可用
if (isFirst(current) && // 检查当前线程是否符合某种条件
compareAndSetState(0, acquires)) { // 尝试以原子方式获取锁
setExclusiveOwnerThread(current); // 设置当前线程为独占锁的所有者
return true; // 返回成功
}
} else if (current == getExclusiveOwnerThread()) { // 如果锁已被当前线程持有
int nextc = c + acquires; // 计算新的锁计数
if (nextc < 0)
throw new Error("Maximum lock count exceeded"); // 检查是否超过最大值
setState(nextc); // 设置新的锁状态
return true; // 返回成功
}
return false; // 如果既不是首次获取也不是重入,则返回失败
}
- 方法声明:
tryAcquire
是一个受保护的最终方法,只能被同一包中的类或子类调用,并且不能被子类覆盖。 - 获取当前线程:存储当前执行该方法的线程对象。
- 获取锁的状态:通过
getState()
读取当前锁的状态。 - 检查并获取锁:如果锁未被持有,尝试以原子操作获取锁,并设置当前线程为所有者。
- 支持重入:如果锁已由当前线程持有,则增加锁的计数,支持重入。
- 异常处理:如果锁计数超过最大值,抛出异常。
- 返回结果:根据操作的结果返回
true
或false
。
整个方法利用了CAS(Compare and Swap)操作来保证对锁状态的修改是原子性的,从而避免了竞争条件的发生。