ReentrantLock加锁流程之cancelAcquire方法
cancelAcquire()方法的作用是取消AQS双向链表中的某个节点
取消节点的流程:
- 节点的线程设置为null
- 往前找到有效节点作为当前节点的prev
- 将waitStatus设置成1,代表本节点将要取消
- 脱离AQS链表,分为以下几种情况
- 当前节点是tail节点
- 当前节点是head的next节点
- 当前节点是中间节点
private void cancelAcquire(Node node) {
// 如果当前节点为null,直接返回
if (node == null)
return;
// 设置当前节点的线程为null
node.thread = null;
// 通过循环,使得当前节点的前一个节点状态一定<=0
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
// 设置当前节点的状态位1,表示即将取消
node.waitStatus = Node.CANCELLED;
if (node == tail && compareAndSetTail(node, pred)) {
// 如果当前节点是tail节点
// compareAndSetTail方法是将tail指向当前节点的前一个节点
// compareAndSetNext方法是将当前节点的前一个节点的next指向null
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// pred != head 说明当前节点是中间节点
// (ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))
// 这段是在判断前一个节点的状态是否为-1,如果不是,并且状态值<=0,则改成-1
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
// 将当前节点的前一个节点的next指向当前节点的下一个节点
// 将当前节点的下一个节点的pred指向当前节点的上一个节点
compareAndSetNext(pred, predNext, next);
} else {
// 当前节点的前一个节点是head
// 则需要将head的next指向当前节点的下一个节点,并且唤醒下一个节点
unparkSuccessor(node);
}
node.next = node;
}
}
总结一下当前节点在不同位置的取消策略
在取消节点之前,先从当前节点往前找有效节点,将当前节点放到有效节点后面,然后再根据当前节点位置执行取消策略
节点在尾部:将前一个有效节点设置成尾节点
节点在中间:判断前一个节点是否是有效节点,如果是,再判断后一个节点是否是有效节点,如果是才会执行取消操作。具体的取消操作是将当前节点的前一个节点与后一个节点连接,当前节点删除
节点在头节点后面:唤醒当前节点后面最近的一个有效节点。这个有效节点唤醒后会执行shouldParkAfterFailedAcquire方法,这个方法会将有效节点前面的无效节点删除掉,包括当前的无效节点
这三个策略可以保证无效节点被删除