Java高频面试之并发编程-23
hello啊,各位观众姥爷们!!!本baby今天又来报道了!哈哈哈哈哈嗝🐶
面试官:ReentrantLock 如何实现公平锁的?
ReentrantLock 公平锁的实现原理
ReentrantLock 的公平锁通过 AQS(AbstractQueuedSynchronizer) 的同步队列(CLH 队列变体)和 严格的顺序检查机制 实现,确保线程按照请求锁的顺序获取锁。以下是其核心实现细节:
1. 公平锁与非公平锁的核心区别
公平锁与非公平锁的唯一差异在于 获取锁时的策略:
- 非公平锁:新线程可以直接尝试抢占锁(即使队列中有线程在等待)。
- 公平锁:新线程必须检查队列中是否有等待线程,若有则排队,严格按 FIFO 顺序获取锁。
2. 公平锁的实现关键
(1) tryAcquire
方法的重写
公平锁的 tryAcquire
方法中,必须先检查同步队列中是否有其他线程在排队,若无才允许尝试获取锁。这是通过 hasQueuedPredecessors()
方法实现的。
protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {// 关键点:只有队列中没有等待线程时,才尝试获取锁if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}} else if (current == getExclusiveOwnerThread()) {// 重入逻辑与非公平锁一致int nextc = c + acquires;setState(nextc);return true;}return false;
}
(2) hasQueuedPredecessors()
方法
该方法判断当前线程是否是队列中的第一个有效节点(即是否有其他线程更早地请求锁):
public final boolean hasQueuedPredecessors() {Node t = tail; // 队列尾节点Node h = head; // 队列头节点Node s;// 队列不为空,且头节点的下一个节点存在,且该节点关联的线程不是当前线程return h != t &&((s = h.next) == null || s.thread != Thread.currentThread());
}
- 返回
true
:队列中存在比当前线程更早等待的线程,当前线程必须排队。 - 返回
false
:队列为空或当前线程是队列中的第一个有效节点,允许尝试获取锁。
3. 公平锁的工作流程
(1) 线程首次获取锁
- 线程调用
lock()
方法。 - 调用
acquire(1)
,触发tryAcquire
。 - 若
state=0
(锁未被占用):- 检查队列中是否有等待线程(
hasQueuedPredecessors()
)。 - 若无等待线程:尝试 CAS 修改
state
为 1,成功则获取锁。 - 若有等待线程:封装为
Node
加入队列尾部,进入阻塞状态。
- 检查队列中是否有等待线程(
(2) 锁释放后唤醒后续线程
- 持有锁的线程调用
unlock()
,释放锁(state=0
)。 - AQS 唤醒队列中第一个有效节点(头节点的后继节点)关联的线程。
- 被唤醒的线程再次尝试
tryAcquire
,此时队列中无更早的等待线程,成功获取锁。
4. 公平锁的严格顺序保证
公平锁通过以下机制确保顺序性:
- 禁止插队:新线程必须检查队列中是否有等待线程,无法直接抢占锁。
- 队列唤醒:锁释放时,只唤醒队列中的第一个有效节点,确保先进先出。
5. 公平锁的代价
- 性能开销:
每次获取锁都需要检查队列状态,增加 CAS 操作和线程切换次数。 - 吞吐量降低:
严格的顺序性可能导致 CPU 空闲(例如,队列中的线程唤醒需要时间)。
6. 代码示例:公平锁的完整获取流程
// 创建公平锁
ReentrantLock fairLock = new ReentrantLock(true);// 加锁过程
fairLock.lock();
try {// 临界区代码
} finally {fairLock.unlock();
}
你想要的技术资料我全都有:https://pan.q删掉汉子uark.cn/s/aa7f2473c65b