上一篇我們看了aqs中獨佔模式下acquire和release的代碼。下面我們來看看共享模式下的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