上一篇詳細的分析了獨占模式下如何對線程進行處理:簡單的總結是Java面向用戶提供了鎖的機制,後面的實現使用了一個同步隊列,由於隊列具有先進先出的特點,把每個線程都構造成為隊列中的節點,每個節點定義一個狀態值,符合狀態的節點(線程)才可以有執行的機會,執行完釋放,後面的線程只能是等待著前面的執行結果進 ...
上一篇詳細的分析了獨占模式下如何對線程進行處理:簡單的總結是Java面向用戶提供了鎖的機制,後面的實現使用了一個同步隊列,由於隊列具有先進先出的特點,把每個線程都構造成為隊列中的節點,每個節點定義一個狀態值,符合狀態的節點(線程)才可以有執行的機會,執行完釋放,後面的線程只能是等待著前面的執行結果進行判斷,每個線程的執行都是獨立的,不能有其他的線程干擾,所以在用戶的角度來看線程是在同步的執行的,並且是獨占式的。
共用式和獨占式的區別主要是能否在同一時刻不同的線程獲取到同步狀態
上圖可以很直觀的看出獨占式和共用式的區別。在對一個資源進行訪問的時候,對讀操作是共用式的,而對寫操作是獨占式的。
下麵接著分析共用模式下線程之間是怎麼實現的?
依然找到方法執行的入口,在上一篇我們找到了這幾種方式的頂層方法。
共用模式同步狀態的獲取:
在acquireShared()
方法中,調用了tryAcquireShared()
返回一個狀態值,進行判斷,獲取成功直接返回,失敗進入等待隊列中。
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
方法執行的順序:
- 調用
tryAcquireShared()
方法嘗試去獲取資源,具體實現是在子類中進行實現,成功直接返回,失敗執行下麵的方法。
- 調用
doAcquireShared()
方法,線程進入等待隊列,等待獲取資源。
tryAcquireShared()
方法是在子類中實現的,這裡不需要討論,但是返回值已經是定義好的。方法返回一個int類型的值,當返回值大於0的時候,表示能夠獲取到同步狀態。
doAcquireShared()方法:
在前面分析過的方法這裡不再分析
private void doAcquireShared(int arg) {
//調用addWaiter方法把線程加入隊列尾
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
if (interrupted)
//如果被中斷過,重新設置中斷
selfInterrupt();
failed = false;
return;
}
}
//判斷線程狀態移除或者掛起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
基本上和獨占式的處理方式一致,根本的體現就是在下麵的這個setHeadAndPropagate()
方法
setHeadAndPropagate():
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
//如果還有資源剩餘,繼續喚醒後面挨著的線程
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
在獨占模式下獲取資源,即使資源還有剩餘,隊列中的後一個線程節點也不會被喚醒執行,只有自己占有才是獨占,而共用式的是有剩餘就會給後面。
parkAndCheckInterrupt():
private final boolean parkAndCheckInterrupt() {
//讓當前對象停止執行,阻塞狀態
LockSupport.park(this);
//檢查中斷
return Thread.interrupted();
}
cancelAcquire():
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
//獲得前驅結點
Node pred = node.prev;
//如果狀態值大於0
while (pred.waitStatus > 0)
//移除當前節點的前驅結點
node.prev = pred = pred.prev;
//設置節點狀態值為CANCELLED
node.waitStatus = Node.CANCELLED;
//如果是尾節點,設置尾節點
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
//當節點既不是尾節點,也不是頭節點的後繼節點時,下麵的這些判斷其實執行的出隊操作起作用的就是compareAndSetNext()方法將pred指向後繼節點
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//如果node是head的後繼節點,則直接喚醒node的後繼節點
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
共用模式同步狀態的釋放:
嘗試釋放資源如果成功,會喚醒後續處於等待狀態中的節點
public final boolean releaseShared(int arg) {
//嘗試釋放資源
if (tryReleaseShared(arg)) {
//喚醒後繼節點
doReleaseShared();
return true;
}
return false;
}
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;
}
}
和獨占式的釋放從代碼上看他們的區別是共用式的釋放方法中有一個迴圈。
引用:https://www.cnblogs.com/waterystone/p/4920797.html
上面方法的流程也比較簡單,一句話:釋放掉資源後,喚醒後繼。跟獨占模式下的release()相似,但有一點稍微需要註意:獨占模式下的tryRelease()在完全釋放掉資源(state=0)後,才會返回true去喚醒其他線程,這主要是基於獨占下可重入的考量;而共用模式下的releaseShared()則沒有這種要求,共用模式實質就是控制一定量的線程併發執行,那麼擁有資源的線程在釋放掉部分資源時就可以喚醒後繼等待結點。例如,資源總量是13,A(5)和B(7)分別獲取到資源併發運行,C(4)來時只剩1個資源就需要等待。A在運行過程中釋放掉2個資源量,然後tryReleaseShared(2)返回true喚醒C,C一看只有3個仍不夠繼續等待;隨後B又釋放2個,tryReleaseShared(2)返回true喚醒C,C一看有5個夠自己用了,然後C就可以跟A和B一起運行。而ReentrantReadWriteLock讀鎖的tryReleaseShared()只有在完全釋放掉資源(state=0)才返回true,所以自定義同步器可以根據需要決定tryReleaseShared()的返回值。
在共用模式下,資源有剩餘就會給後面的鄰居,不會自己占有,這是和獨占式的根本區別。