十一、ReentrantLock加锁流程之cancelAcquire方法

66 阅读2分钟

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方法,这个方法会将有效节点前面的无效节点删除掉,包括当前的无效节点

这三个策略可以保证无效节点被删除