ReentrantLock释放锁流程之unlock方法
unlock方法的执行逻辑:
- 首先判断当前线程是否是持有锁的线程,如果不是抛异常
- 如果是持有锁的线程,state值-1
- state值-1后,如果不为0,说明当前线程还在持有锁,并且方法结束
- 如果state值为0,说明锁被释放
- 查看head节点的状态是否为0,如果是,说明AQS链表中没有等待唤醒的线程
- 如果为-1,则需要唤醒后面的线程
- 唤醒线程前,先将head节点状态改成0,然后找到有效的节点唤醒
- 如果head后面的节点是有效节点,则唤醒;如果不是,则从后往前的方式寻找有效节点,因为从前往后寻找,会出现在状态为取消的节点处发现next节点为null的情况,这样寻找过程就被迫中断了
注意:线程在执行unlock方法后,不会主动清理自己在AQS中的节点,AQS的设计是延迟清理+后继节点覆盖的策略
public void unlock() {
// 公平锁和非公平锁是相同的流程
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
// state值-1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
// 如果持有锁的线程不是当前线程,抛异常
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 锁被释放
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
// 获取head节点的状态
int ws = node.waitStatus;
if (ws < 0)
// 将head节点状态设置成0
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
// head节点后面的节点状态为取消状态,才会进入这里
s = null;
// 从tail节点开始往前寻找有效的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 唤醒有效节点
LockSupport.unpark(s.thread);
}
总结下AQS队列上的无效节点是如何被清除的:
第一种情况:队列上都是调用lock方法而堵塞的节点
这种情况下,持有锁的线程释放锁后,会唤醒head节点后面的节点,此节点会变成新的head节点,并且线程获得锁
第二种情况:队列中有tryLock(time)方法而堵塞的节点
这种情况下,如果线程正常获取到锁,那么节点会像第一种情况一样变成新的head节点;
如果线程是因为时间到了被唤醒,那么节点的状态会变成1,此时,如果节点处于队列的尾部或者中间,那么会调整双向链表节点指针,将此节点断开,让有效节点前后相连;
如果节点处于head节点后面,那么会先将head节点状态设置为0,然后唤醒自身后面的有效节点的线程。被唤醒的线程如果获取锁成功,那么自身节点会变成新的head节点,如果获取锁失败,会调用shouldParkAfterFailedAcquire方法,将前面的无效节点去掉,并且将head节点的状态从0改成-1