十三、ReentrantLock释放锁流程之unlock方法

74 阅读3分钟

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