可重入锁
一、什么是可重入锁
可重入锁(Reentrant Lock)是一种允许同一个线程 重复获取同一把锁 的锁机制。
简单来说,当一个线程已经持有了某个锁,它在未释放该锁之前再次请求同一锁时,系统会允许它继续获取,而不会被阻塞或死锁。
二、为什么需要可重入锁
在多线程编程中,一个线程可能在调用某个同步方法时,又间接调用了另一个也需要同一把锁的同步方法。如果没有可重入机制,线程会因为再次尝试获取自己已持有的锁而陷入死锁。
例如:
public synchronized void methodA() {
System.out.println("Method A");
methodB(); // 这里再次请求同一把锁
}
public synchronized void methodB() {
System.out.println("Method B");
}上面的两个方法都被 synchronized 修饰,意味着它们都需要获取对象的锁。如果没有可重入特性,线程在 methodA() 中调用 methodB() 时会再次尝试获取同一把锁,从而自己阻塞自己,导致死锁。
而由于 Java 的内置锁(synchronized)和 ReentrantLock 都是可重入锁,因此该代码能正常执行。
三、可重入锁的实现原理
可重入锁的核心原理是 锁持有计数(Hold Count)机制。
- 每当一个线程第一次获取锁时,锁的持有计数设为 1。
- 当该线程再次获取相同的锁时,计数器加 1。
- 当线程释放锁时,计数器减 1。
- 只有当计数器降为 0 时,锁才真正被释放。
这种设计保证了同一个线程可以安全地多次进入加锁代码块,而不会造成死锁。
四、可重入锁的应用场景
递归调用
在递归函数中可能会多次调用相同的同步代码块,如果没有可重入特性会造成死锁。父子方法调用
当父方法和子方法都需要加同一锁时,可重入锁可以避免线程被自己阻塞。多层封装场景
在框架或组件开发中,底层封装可能已经加锁,调用层无需担心重复加锁问题。
五、可重入锁在 Java 中的实现
1. synchronized
Java 内置关键字 synchronized 天然支持可重入。
JVM 会在对象头(Object Header)中维护锁的持有线程标识与重入次数,当同一线程再次进入同步块时,计数加一,退出时减一。
2. ReentrantLock
ReentrantLock 是 java.util.concurrent.locks 包中的显式可重入锁,它提供了比 synchronized 更灵活的锁控制能力,如:
- 可响应中断的锁请求;
- 可设置超时时间;
- 可实现公平锁。
使用示例:
Lock lock = new ReentrantLock();
lock.lock();
try {
System.out.println("第一次加锁");
lock.lock(); // 第二次加锁(同一线程)
try {
System.out.println("可重入成功");
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}输出结果:
第一次加锁
可重入成功六、可重入锁的优点
- 避免死锁:允许同一线程多次进入同步区域而不会阻塞。
- 简化编程逻辑:开发者无需担心多层调用时锁重复加的问题。
- 增强灵活性:显式可重入锁(如
ReentrantLock)可提供更丰富的控制方式。
七、总结
可重入锁是一种允许同一线程多次获取同一锁的机制,通过 锁计数器 来记录持有次数,防止线程被自身阻塞。
在 Java 中,synchronized 和 ReentrantLock 都属于可重入锁,是编写线程安全代码、避免死锁的重要工具。