1. 概述
本文将带你深入了解 Binary Semaphore(二值信号量) 与 Reentrant Lock(可重入锁) 的使用场景与核心差异。我们将从机制、所有权、灵活性等多个维度进行对比,帮助你判断在不同场景下应该选择哪种同步工具。
2. 什么是 Binary Semaphore?
Binary Semaphore 是一种用于控制单一资源访问的同步机制。它本质上是一种互斥机制,只允许一个线程进入临界区。
Binary Semaphore 内部只维护一个许可(permit),因此它只有两种状态:
- ✅ 1 个许可可用(未被占用)
- ❌ 0 个许可可用(已被占用)
我们可以通过 Java 中的 Semaphore
类来实现一个 Binary Semaphore:
Semaphore binarySemaphore = new Semaphore(1);
try {
binarySemaphore.acquire();
assertEquals(0, binarySemaphore.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
binarySemaphore.release();
assertEquals(1, binarySemaphore.availablePermits());
}
acquire()
方法会消耗一个许可release()
方法会释放一个许可
⚠️ Semaphore 还支持 fairness
参数。设置为 true
时,将按照线程等待时间顺序分配许可,防止线程饥饿:
Semaphore binarySemaphore = new Semaphore(1, true);
3. 什么是 Reentrant Lock?
Reentrant Lock 是一种可重入的互斥锁机制,允许同一个线程多次获取锁而不会导致死锁。
每次线程成功获取锁时,锁的持有计数器 hold count
会加一;每次释放锁时,计数器减一。只有当计数器归零时,锁才真正被释放。
示例代码如下:
ReentrantLock reentrantLock = new ReentrantLock();
try {
reentrantLock.lock();
assertEquals(1, reentrantLock.getHoldCount());
assertEquals(true, reentrantLock.isLocked());
} finally {
reentrantLock.unlock();
assertEquals(0, reentrantLock.getHoldCount());
assertEquals(false, reentrantLock.isLocked());
}
如果线程多次获取锁,必须同样多次释放锁才能真正解锁:
reentrantLock.lock();
reentrantLock.lock();
assertEquals(2, reentrantLock.getHoldCount());
assertEquals(true, reentrantLock.isLocked());
reentrantLock.unlock();
assertEquals(1, reentrantLock.getHoldCount());
assertEquals(true, reentrantLock.isLocked());
reentrantLock.unlock();
assertEquals(0, reentrantLock.getHoldCount());
assertEquals(false, reentrantLock.isLocked());
同样,ReentrantLock 也支持公平锁模式:
ReentrantLock reentrantLock = new ReentrantLock(true);
4. Binary Semaphore vs Reentrant Lock 对比
4.1. 工作机制 ✅
特性 | Binary Semaphore | Reentrant Lock |
---|---|---|
类型 | 信号机制(signaling mechanism) | 锁机制(locking mechanism) |
用途 | 控制资源访问 | 提供互斥保护 |
4.2. 所有权 ✅
特性 | Binary Semaphore | Reentrant Lock |
---|---|---|
是否有所有者 | ❌ 无所有者 | ✅ 有明确所有者(最后获取锁的线程) |
4.3. 可重入性 ✅
特性 | Binary Semaphore | Reentrant Lock |
---|---|---|
是否支持重入 | ❌ 不支持(重入会导致死锁) | ✅ 支持(同一线程可多次获取) |
4.4. 灵活性 ✅
特性 | Binary Semaphore | Reentrant Lock |
---|---|---|
是否灵活 | ✅ 高(可自定义同步逻辑) | ❌ 低(固定锁机制) |
4.5. 修改权限 ✅
特性 | Binary Semaphore | Reentrant Lock |
---|---|---|
谁能释放 | ✅ 任意线程 | ❌ 仅持有锁的线程 |
4.6. 死锁恢复 ✅
特性 | Binary Semaphore | Reentrant Lock |
---|---|---|
是否支持非所有者释放 | ✅ 是(可用于死锁恢复) | ❌ 否(死锁恢复困难) |
5. 总结
简单来说:
- Binary Semaphore 是一种无所有权的信号机制,适合需要灵活控制并发访问的场景,也便于实现自定义锁与死锁恢复。
- Reentrant Lock 是一种基于所有者的可重入锁,适合需要严格互斥控制的场景,尤其适用于替代 synchronized 的高性能锁。
📌 建议使用场景:
- 如果你需要灵活控制并发访问、支持任意线程释放资源,选 Binary Semaphore。
- 如果你只是想保护一段代码的互斥访问,并且希望支持重入,选 Reentrant Lock。
源码示例可参考:GitHub 项目地址