什么是可重入锁?
可重入锁(Reentrant Lock)是一种锁机制,主要用于解决线程或进程间的锁重入问题,允许同一个线程在持有锁的情况下,可以再次请求并持有该锁,而不会发生阻塞或者死锁。
1. 基本概念
- 若一个线程已经获取了锁,它无需等待释放后再次获取,可以直接进入锁保护的代码块。
- 可重入锁一般通过记录锁的持有者和锁的计数器实现:当线程第一次获取锁时,计数器值为 1;如果线程再次获取锁,计数器值会递增;当线程释放锁时,计数器值会递减,直到计数器为 0,锁才真正释放。
2. 特性
- 持有者标识:记录锁当前的持有者(通常是线程 ID)。
- 锁计数:记录锁被持有的次数。
- 递归调用友好:允许线程在递归调用函数中继续获取已经持有的锁。
这种锁的设计避免了因为递归调用或重复进入同一个代码块而造成的死锁。
3. 例子说明
假设没有可重入锁:
线程第一次获取锁后,进入代码块,再次调用时试图获取锁,由于锁已经被持有且不可重入,线程会因为锁未释放而阻塞,最终导致死锁。
示例应用场景:
比如在递归函数内的同步操作,如果锁不支持可重入:
public synchronized void doSomething() { doSomethingElse(); //再次尝试获取锁,死锁! } public synchronized void doSomethingElse() { //其他逻辑 }
可重入支持:
通过计数器机制允许在上例的递归调用中,线程多次进入“同步块”,不会发生死锁。
4. 实现原理
可重入锁的主要实现机制有以下内容:
- 所有权:锁中记录当前持有的线程标识(如
ThreadID
)。 - 计数器:
- 第一次获取锁时,计数器值 +1;
- 同一个线程再次获取锁时,计数器递增;
- 每次释放锁时,计数器递减;
- 当计数器减为 0 时,锁才真正释放。
5. 可重入锁的应用
可重入锁广泛应用于以下场景:
1. 递归函数
在递归函数调用时,保证锁不会导致死锁。
2. 嵌套调用
方法 A 持有锁,但在方法 A 内调用另一个同步的方法 B,B 也需要获取锁。可重入锁允许这种嵌套逻辑。
6. 在不同语言中的实现
Java
在 Java 中,ReentrantLock
是标准库中对可重入锁的实现:
import java.util.concurrent.locks.ReentrantLock; public class MyReentrantLockExample { private final ReentrantLock lock = new ReentrantLock(); public void outerMethod() { lock.lock(); try { innerMethod(); } finally { lock.unlock(); } } public void innerMethod() { lock.lock(); try { // Do work } finally { lock.unlock(); } } }
Python
Python 的 threading.RLock
也是可重入锁的实现:
import threading lock = threading.RLock() def outer(): with lock: inner() def inner(): with lock: print("Inner method executed")
Golang
Golang 默认的 sync.Mutex
锁不是可重入锁。如果需要实现可重入锁,需要手动处理计数器和持有者。一个简单实现如下:
import ( "sync" "errors" ) type ReentrantLock struct { mu sync.Mutex holder int64 recCount int } // Lock implementation func (rl *ReentrantLock) Lock(threadID int64) error { rl.mu.Lock() if rl.holder == threadID { rl.recCount++ rl.mu.Unlock() return nil } if rl.recCount == 0 { rl.holder = threadID rl.recCount++ rl.mu.Unlock() return nil } rl.mu.Unlock() return errors.New("lock already acquired by another thread") } // Unlock implementation func (rl *ReentrantLock) Unlock(threadID int64) { rl.mu.Lock() defer rl.mu.Unlock() if rl.holder == threadID { rl.recCount-- if rl.recCount == 0 { rl.holder = 0 } } }
7. 与不可重入锁的对比
特性 | 可重入锁 | 不可重入锁 |
---|---|---|
是否允许重入 | 是,可多次重入 | 否,同线程第二次获取会阻塞 |
避免死锁设计 | 支持递归调用,避免死锁 | 偶尔容易导致死锁 |
实现复杂度 | 较高,需要计数器和标识 | 简单,直接进行加锁、解锁 |
8. Redis 分布式锁与可重入
如果需要在分布式场景中实现类似功能(可重入分布式锁),需要额外设计持有者标识和计数器,比如依赖 Redis 的 Lua 脚本或 Redisson
实现类似功能。
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接