對比
獨(dú)占模式 | 共享模式 |
---|---|
acquire(int arg) | acquireShared(int arg) |
acquireInterruptibly(int arg) | acquireSharedInterruptibly(int arg) |
tryAcquireNanos(int arg, long nanosTimeout) | tryAcquireSharedNanos(int arg, long nanosTimeout) |
release(int arg) | releaseShared(int arg) |
上面列出了獨(dú)占模式和共享模式獲取鎖和釋放鎖的入口方法.我們對比分析就能很清楚的了解它們之間的不同.
共享模式獲取鎖
同獨(dú)占模式一樣,獲取鎖的入口方法我們從acquireShared(int arg)
開始,另外還有兩個入口如果有興趣自己分析即可.
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
上面代碼第一步開始調(diào)用tryAcquireShared(arg)
嘗試獲取共享資源,而在獨(dú)占模式中是調(diào)用tryAcquire(arg)
.同獨(dú)占模式一樣這個方法也需要同步器自己去實(shí)現(xiàn).這個方法返回值為剩余資源的個數(shù),主要可以分為三種情況:
-
0
:獲取共享資源成功,但是沒有剩余的資源了. -
>0
:獲取資源成功,還有剩余資源 -
<0
:獲取資源失敗.
如果獲取資源成功則直接返回了,如果失敗了則進(jìn)入doAcquireShared(arg)
,而獨(dú)占模式下則是進(jìn)入acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
.如果看了之前的文章的人知道下一步是干嘛了.下一個就是要將自己加入到隊(duì)列的尾部然后掛起等待.
private void doAcquireShared(int arg) {
//將自己添加到隊(duì)列的尾部,這里在獨(dú)占模式已分析過了,如果想了解請看之前獨(dú)占模式相關(guān)處
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) {
//將自己設(shè)置為頭節(jié)點(diǎn),并傳播(后面會解釋傳播)
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//嘗試獲取資源失敗,后續(xù)操作同獨(dú)占鎖
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
上面的代碼與獨(dú)占模式下整體上并沒有太大差別,主要流程如下:
- 調(diào)用
addWaiter
將自己添加到隊(duì)列的尾部 - 獲取當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn),然后看前節(jié)點(diǎn)是不是頭節(jié)點(diǎn).
- 如果當(dāng)前節(jié)點(diǎn)的前節(jié)點(diǎn)是頭節(jié)點(diǎn),自己就有資格去嘗試獲取資源.如果獲取失敗了就進(jìn)行后面操作邏輯.如果嘗試獲取資源成功了,則會調(diào)用
setHeadAndPropagate(node, r)
將自己設(shè)置成頭節(jié)點(diǎn)并往后傳播.關(guān)于setHeadAndPropagate
這個方法后面會細(xì)講. - 第2步判斷當(dāng)前節(jié)點(diǎn)不是老二或者第3步獲取資源失敗就會進(jìn)入這個邏輯.這個邏輯主要做兩件事:
將前置節(jié)點(diǎn)設(shè)置waitStatuss設(shè)置成-1
和讓當(dāng)前線程掛起
.這個邏輯與獨(dú)占鎖中的并沒有差別.
在第3步中調(diào)用tryAcquireShared
獲取到資源后的操作與獨(dú)占模式中不一樣.在獨(dú)占模式中是調(diào)用setHead
將自己設(shè)置成頭節(jié)點(diǎn),具體代碼如下:
//獨(dú)占模式只會將當(dāng)前節(jié)點(diǎn)設(shè)置成頭節(jié)點(diǎn)
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
而在共享模式中,它會調(diào)用setHeadAndPropagate
不僅僅只是將當(dāng)前節(jié)點(diǎn)設(shè)置成頭節(jié)點(diǎn),還有將當(dāng)前節(jié)點(diǎn)的后繼幾點(diǎn)繼續(xù)喚醒,具體代碼如下:
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
//喚醒等待獲取共享資源的線程,后面細(xì)說.目前知道這個方法就是去喚醒等待的節(jié)點(diǎn)
doReleaseShared();
}
}
收先要明確為什么能進(jìn)入這個方法,這個方法中的node
一定是代表它是當(dāng)前成功獲取了共享資源的線程,propagate
代表了當(dāng)前線程獲取了共享資源后還剩余的線程數(shù).這個方法一定是當(dāng)前線程成功獲取了共享資源才會進(jìn)來的.首先它通過一個h
用來保存舊的head
節(jié)點(diǎn),然后將自己設(shè)置成head
節(jié)點(diǎn).然后進(jìn)入后面這一堆的判斷.下面我們來分析這一系列的判斷
-
propagate > 0
:當(dāng)前線程獲取資源后發(fā)現(xiàn)還有剩余資源,那么這個時(shí)候就需要喚醒等待的節(jié)點(diǎn).這個很好理解,因?yàn)槭枪蚕砟J?當(dāng)資源有多余的時(shí)候就喚醒其他等待資源的線程.要不然怎么叫共享呢? -
h.waitStatus < 0
:這個代表老的head節(jié)點(diǎn)
后面的節(jié)點(diǎn)可以被喚醒. -
(h = head) == null || h.waitStatus < 0
:這個代表新的head節(jié)點(diǎn)
后面的節(jié)點(diǎn)需要被喚醒.
總結(jié)來說就是兩點(diǎn):當(dāng)propagate > 0
時(shí)說明資源可用所以喚醒節(jié)點(diǎn),而h.waitStatus < 0
說明不管是新的頭結(jié)點(diǎn)還是老的頭節(jié)點(diǎn)只要它的waitStatus < 0
都需要喚醒節(jié)點(diǎn).
關(guān)于
h == null
這個條件我覺得不會生效.因?yàn)檫M(jìn)入該方法前addWaiter
已經(jīng)調(diào)用了.CLH隊(duì)列中至少會存在一個節(jié)點(diǎn)所以我覺得這個條件不會生效.目前我也想不出什么情況下h == null
.
上面就是整個共享模式下獲取資源的整個過程.整體上與獨(dú)占模式下差別不太大.都是獲取到了直接返回執(zhí)行業(yè)務(wù)代碼,獲取失敗則進(jìn)入阻塞.但是最大的不同點(diǎn)在于:共享模式下獲取共享資源成功的情況下同時(shí)還會去喚醒等待的線程,而在獨(dú)占模式下是不會的.
共享模式下釋放鎖
共享鎖的釋放的方法入口是releaseShared
,它的源代碼如下:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
該方法的實(shí)現(xiàn)比較簡單,tryReleaseShared(arg)(arg)
這個是同步器需要自己實(shí)現(xiàn)的方法.釋放鎖時(shí)最核心的方法就是doReleaseShared()
.該方法在之前獲取共享資源時(shí)調(diào)用過,現(xiàn)在在釋放鎖的時(shí)候也調(diào)用過
.我們來看該方法的實(shí)現(xiàn):
private void doReleaseShared() {
for (;;) {
//使用h保存久的頭節(jié)點(diǎn)
Node h = head;
//說明隊(duì)列種存在兩個以上的節(jié)點(diǎn)
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
//ws = -1 說明需要喚醒后續(xù)節(jié)點(diǎn),將h節(jié)點(diǎn)設(shè)置為0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
//在獨(dú)占模式已經(jīng)分析過了
unparkSuccessor(h);
}
//可能該節(jié)點(diǎn)被其他線程修改成0
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
//說明上面的條件都沒有滿足的,所以退出
if (h == head)
break;
}
}
上面的代碼主要就是設(shè)置頭節(jié)點(diǎn)狀態(tài)為0,然后喚醒后續(xù)的節(jié)點(diǎn).其中關(guān)于PROPAGATE
狀態(tài)的引入可以參照PROPAGATE狀態(tài)存在的意義.
小結(jié)
上面內(nèi)容基于獨(dú)占模式的對比做了共享模式下鎖的獲取和釋放.整體流程和獨(dú)占模式下大致相同,最大的不同點(diǎn)就在于共享模式成功獲取資源后還可能會喚醒后續(xù)等待的線程,而獨(dú)占模式是不會這樣做的
.