上一篇我们看了aqs中独占模式下acquire和release的代码(https://blog.csdn.net/a6822342/article/details/84839391)。下面我们来看看共享模式下的acquireShared和releaseShared的代码。
首先先看acquireShared的代码
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
首先看能不能直接获得资源,也就是执行tryAcquireShared(arg)方法,如果没有取得资源,则返回是负数,如果取得了资源但是后续的资源可以再进行获取则返回的是0,如果返回的是正数,那么代表着获取成功并且还有剩余资源,别的线程也能进行获取。Ps:tryAcquireShared方法需要子类自己去实现。
如果返回值小于0,那么通过doAcquireShared(arg)进入等待队列,等待获取资源。
下面我们来看看doAcquireShared(arg)方法。
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//找先驱结点
if (p == head) {
int r = tryAcquireShared(arg);//尝试获取资源
if (r >= 0) {//获取到了资源
setHeadAndPropagate(node, r);//设置当前结点为头结点,然后去唤醒后续结点
p.next = null; // help GC 释放头结点,等待GC
if (interrupted)
selfInterrupt();
failed = false;//获取到资源
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)//如果最后没有获取到资源,则cancel
cancelAcquire(node);
}
}
final Node node = addWaiter(Node.SHARED);首先还是把该线程新建一个结点,然后加入到队列中去,这一块和独占模式下的addWaiter代码差不多,不同的是结点的模式不一样。这里就不重复赘述了,可以去看https://blog.csdn.net/a6822342/article/details/84839391
定义一个标记failed,表示是否成功拿到资源的标记。
定义一个是否被中断的标记interrupted,默认是false。
然后就循环的去找当前结点的前驱结点,看它是不是头结点,如果是头结点的话,就让该前驱结点去获取资源,如果获取到资源的话,那么返回值r的值是大于等于0的,然后就会通过setHeadAndPropagate(node, r)方法把后续结点唤醒。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
这里是通过shouldParkAfterFailedAcquire和parkAndCheckInterrupt来判断自己是不是可以进入休息状态了,如果在休息状态的时候被中断过,则把interrupted设置为true,然后当自己成为头结点的时候返回。(和独占模式下的acquireQueued()方法内的意思一样)
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();//主要功能唤醒后续结点
}
}
setHead(node)表示为把node设置为head结点,然后如果当前node获取完资源还有剩余(propagate>0)的话,就去唤醒后继的结点,也就是源码中这句英文的意思:Propagation was indicated by caller 。如果当前头结点的状态是<0,意味着它是SINGAL和Propagate,那么也去唤醒后继结点。如果头结点为空(可能在其它线程中被释放了),那么也唤醒后继结点来获取资源。
那么此时,s作为传进来的结点的后继结点,执行if (s == null || s.isShared())的判断,如果s为空(当前结点为空的话,当然要找后继结点)或者s是shared模式的话,也要唤醒后继结点,则执行doReleaseShared()方法去唤醒后继。
doReleaseShared()的代码放在将releaseShared()的代码里面分析。
下面我们来看看releaseShared()
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
首先去尝试释放资源tryReleaseShared(arg),如果释放成功了,就代表有资源空闲出来,那么就用doReleaseShared()去唤醒后续结点。
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
在自旋的阶段,每一次循环的过程都是首先获得头结点,如果头结点不为空且不为尾结点(阻塞队列里面只有一个结点),那么先获得该节点的状态,如果是SIGNAL的状态,则代表它需要有后继结点去唤醒,首先将其的状态变为0,因为是要释放资源了,它也不需要做什么了,所以转变为初始状态,然后去唤醒后继结点unparkSuccessor(h),如果结点状态一开始就是0,那么就给他转换成PROPAGATE状态,保证在后续获取资源的时候,还能够向后面传播(这一块不明白)。
如果在自旋的过程中,头结点改变了,那么就退出循环过程。
参考文章:
https://blog.csdn.net/liu88010988/article/details/50799819?utm_source=blogxgwz6
http://www.php.cn/java-article-372194.html