二十六、ReentrantReadWriteLock之唤醒AQS中读锁流程

45 阅读2分钟

ReentrantReadWriteLock之唤醒AQS中读锁流程

tryAcquireShared方法是尝试获取锁,获取失败返回-1,成功返回1,不会在失败的时候将节点放到同步队列中排队。失败后的流程在doAcquireShared方法中执行。

流程概述:

  1. 封装当前线程为共享锁节点,并放入同步队列中
  2. 拿到当前节点的前一个节点,判断是否为head
  3. 如果是,尝试获取读锁资源,判断是否获取成功
  4. 如果获取成功,判断当前节点的下一个节点是否是读线程,如果是则唤醒,
  5. 并且循环执行,直到下一个节点是写线程为止
  6. 如果获取失败,线程被挂起
  7. 如果当前节点的前一个节点不是head,线程也是被挂起
private void doAcquireShared(int arg) {
	// 封装共享锁Node节点
	final Node node = addWaiter(Node.SHARED);
	boolean failed = true;
	try {
		boolean interrupted = false;
		for (;;) {
			// 拿到当前节点的前一个节点
			final Node p = node.predecessor();
			if (p == head) {
				// 前一个节点是head节点,尝试获取读锁资源
				int r = tryAcquireShared(arg);
				if (r >= 0) {
					// 获取读锁资源成功
					// setHeadAndPropagate方法是检查当前节点后面一个节点是否是读线程
					// 如果是读线程,同样要唤醒
					// 而且会将当前节点设置成头节点
					setHeadAndPropagate(node, r);
					p.next = null; 
					if (interrupted)
						selfInterrupt();
					failed = false;
					return;
				}
			}
			// 挂起线程
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

private void setHeadAndPropagate(Node node, int propagate) {
	Node h = head; 
	setHead(node);
	// propagate > 0这个判断条件是为了解决JDK1.5的BUG
	// 进入这个方法,入参propagate一定是大于0的
	if (propagate > 0 || h == null || h.waitStatus < 0 ||
		(h = head) == null || h.waitStatus < 0) {
		// 当前线程拿到读锁,必会进入这里
		Node s = node.next;
		if (s == null || s.isShared())
			// 如果下一个节点是读线程,则唤醒
			doReleaseShared();
	}
}

private void doReleaseShared() {
	for (;;) {
		Node h = head;
		if (h != null && h != tail) {
			int ws = h.waitStatus;
			if (ws == Node.SIGNAL) {
				if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
					continue;     
				unparkSuccessor(h);
			}
			else if (ws == 0 &&
					 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
				continue;        
		}
		if (h == head)      
			break;
	}
}