ReentrantLock
通过内部的队列管理机制来实现公平锁。公平锁意味着线程会按照请求锁的顺序获取锁,避免了“饥饿”现象。下面是 ReentrantLock
实现公平锁的具体方式:
1. 公平锁的定义
在公平模式下,ReentrantLock
会将等待获取锁的线程放入一个 FIFO(先进先出)队列中。当当前锁被释放时,会优先唤醒等待队列中最先请求的线程,确保线程获取锁的顺序是公平的。
2. 实现机制
a. 继承 AQS
ReentrantLock
的公平锁实现是通过继承 AQS 的一个子类 FairSync
来完成的。FairSync
重写了 AQS 中的一些关键方法,以实现公平策略。
b. 重写 tryAcquire
方法
在 FairSync
中,tryAcquire(int acquires)
方法会检查当前锁是否被其他线程占用。如果锁未被占用,线程可以直接获取锁。如果锁被占用,公平锁会检查等待队列中是否有线程在等待:
static final class FairSync extends Sync {
// 重写 tryAcquire 以实现公平性
@Override
protected boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果锁未被占用,尝试获取锁
if (c == 0) {
if (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;
}
// 此处实现公平性
// 将当前线程加入到等待队列中(如果未持有锁的情况下)
if (!hasQueuedPredecessors()) {
if (compareAndSetState(0, acquires)) { // 尝试获取锁
setExclusiveOwnerThread(current);
return true;
}
}
return false;
}
}
hasQueuedPredecessors()
方法是判断当前线程是否有前驱线程在等待队列中。如果有其他线程在等待,那么当前线程必须等待,直到前面的线程获取锁后才能尝试获取。
c. 线程等待队列的管理
当线程请求锁失败并且 hasQueuedPredecessors()
返回 true
时,线程会被放入 AQS 提供的等待队列。这样,后续的线程请求将会被排列在队列中,确保按照请求的顺序:
- 当锁被释放时,AQS 会自动唤醒队列中的第一个线程,并使其能够尝试获取锁。
3. 获取锁的方法
lock()
方法会调用tryAcquire(1)
来获取锁。由于FairSync
中的实现,后续调用lock()
的线程会在等待队列中排队,直到锁被释放。
4. 总结
ReentrantLock
通过继承 AQS 的 FairSync
类,实现了公平锁的特性。线程请求锁的顺序是FIFO的,从而避免了潜在的饥饿现象。公平锁确保了线程的公平性,但可能导致系统性能的下降,因为可能会出现更高的上下文切换和调度延迟。
如果你有其他问题或需要进一步探讨,请随时在评论区留言!