摘要:主要講解方法共享式獲取同步狀態(tài),返回值表示獲取成功,反之則失敗。源碼分析同步器的和方法請求共享鎖的入口當并且時才去才獲取資源獲取鎖以共享不可中斷模式獲取鎖將當前線程一共享方式構建成節(jié)點并將其加入到同步隊列的尾部。
一、寫在前面
上篇給大家聊了獨占式的源碼,具體參見《J.U.C|AQS獨占式源碼分析》
這一章我們繼續(xù)在AQS的源碼世界中遨游,解讀共享式同步狀態(tài)的獲取和釋放。
二、什么是共享式共享式與獨占式唯一的區(qū)別是在于同一時刻可以有多個線程獲取到同步狀態(tài)。
我們以讀寫鎖為例來看兩者,一個線程在對一個資源文件進行讀操作時,那么這一時刻對于文件的寫操作均被阻塞,而其它線程的讀操作可以同時進行。
當寫操作要求對資源獨占操作,而讀操作可以是共享的,兩種不同的操作對同一資源進行操作會是什么樣的?看下圖
共享式訪問資源,其他共享時均被允許,而獨占式被阻塞。
獨占式訪問資源時,其它訪問均被阻塞。
通過讀寫鎖給大家一起溫故下獨占式和共享式概念,上一節(jié)我們已經(jīng)聊過獨占式,本章我們主要聊共享式。
主要講解方法
protected int tryAcquireShared(int arg);共享式獲取同步狀態(tài),返回值 >= 0 表示獲取成功,反之則失敗。
protected boolean tryReleaseShared(int arg): 共享式釋放同步狀態(tài)。
三、核心方法分析 3.1 同步狀態(tài)的獲取public final void acquireShared(int arg)
共享式獲取同步狀態(tài)的頂級入口,如果當前線程未獲取到同步狀態(tài),將會加入到同步隊列中等待,與獨占式唯一的區(qū)別是在于同一時刻可以有多個線程獲取到同步狀態(tài)。
方法源碼
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
方法函數(shù)解析
tryAcquireShared(arg):獲取同步狀態(tài),返回值大于等于0表示獲取成功,否則失敗。
doAcquireShared(arg):共享式獲取共享狀態(tài),包含構建節(jié)點,加入隊列等待,喚醒節(jié)點等操作。
源碼分析
同步器的 acquireShared 和 doAcquireShared 方法
//請求共享鎖的入口 public final void acquireShared(int arg) { // 當state != 0 并且tryAcquireShared(arg) < 0 時才去才獲取資源 if (tryAcquireShared(arg) < 0) // 獲取鎖 doAcquireShared(arg); }
// 以共享不可中斷模式獲取鎖 private void doAcquireShared(int arg) { // 將當前線程一共享方式構建成 node 節(jié)點并將其加入到同步隊列的尾部。這里addWaiter(Node.SHARED)操作和獨占式基本一樣, final Node node = addWaiter(Node.SHARED); // 是否成功標記 boolean failed = true; try { // 等待過程是否被中斷標記 boolean interrupted = false; 自旋 for (;;) { // 獲取當前節(jié)點的前驅節(jié)點 final Node p = node.predecessor(); // 判斷前驅節(jié)點是否是head節(jié)點,也就是看自己是不是老二節(jié)點 if (p == head) { // 如果自己是老二節(jié)點,嘗試獲取資源鎖,返回三種狀態(tài) // state < 0 : 表示獲取資源失敗 // state = 0: 表示當前正好線程獲取到資源, 此時不需要進行向后繼節(jié)點傳播。 // state > 0: 表示當前線程獲取資源鎖后,還有多余的資源,需要向后繼節(jié)點繼續(xù)傳播,獲取資源。 int r = tryAcquireShared(arg); // 獲取資源成功 if (r >= 0) { // 當前節(jié)點線程獲取資源成功后,對后繼節(jié)點進行邏輯操作 setHeadAndPropagate(node, r); // setHeadAndPropagate(node, r) 已經(jīng)對node.prev = null,在這有對p.next = null; 等待GC進行垃圾收集。 p.next = null; // help GC // 如果等待過程被中斷了, 將中斷給補上。 if (interrupted) selfInterrupt(); failed = false; return; } } // 判斷狀態(tài),尋找安全點,進入waiting狀態(tài),等著被unpark()或interrupt() if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
在acquireShared(int arg)方法中,同步器調(diào)用tryAcquireShared(arg)方法獲取同步狀態(tài),返回同步狀態(tài)有兩種。
當同步狀態(tài)大于等于0時: 表示可以獲取到同步狀態(tài),退出自旋,在doAcquireShared(int arg)方法中可以看到節(jié)點獲取資源退出自旋的條件就是大于等于0
小于0會加入同步隊列中等待被喚醒。
addWaiter和enq方法
// 創(chuàng)建節(jié)點,并將節(jié)點加入到同步隊列尾部中。 private Node addWaiter(Node mode) { // 以共享方式為線程構建Node節(jié)點 Node node = new Node(Thread.currentThread(), mode); // 嘗試快速加入到隊列尾部 Node pred = tail; if (pred != null) { node.prev = pred; // CAS保證原子操作,將node節(jié)點加入到隊列尾部 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 快速加入失敗,走 enq(node)方法 enq(node); return node; }
//以自旋的方式,將node節(jié)點加入到隊列的尾部 private Node enq(final Node node) { // 自旋 for (;;) { // 獲取尾部節(jié)點 Node t = tail; // 如果tail節(jié)點為空, 說明同步隊列還沒初始化,必須先進行初始化 if (t == null) { // Must initialize // CAS保證原子操作, 新建一個空 node 節(jié)點并將其設置為head節(jié)點 if (compareAndSetHead(new Node())) // 設置成功并將tail也指向該節(jié)點 tail = head; } else { // 將node節(jié)點加入到隊列尾部 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
這兩個方法和獨占式的基本相同,注釋中都標明了,在這就不多做解釋了。
獲取資源成功后對后繼節(jié)點的操作setHeadAndPropagate方法
private void setHeadAndPropagate(Node node, int propagate) { // 記錄老的head節(jié)點,以便核對 Node h = head; // Record old head for check below // 將node 設置成head節(jié)點 setHead(node); // 這里表示: 如果資源足夠(propagate > 0)或者舊頭節(jié)點為空(h == null)或者舊節(jié)點的waitStatus為 SIGNAL(-1) 或者 PROPAGATE(-3)(h.waitStatus < 0) // 或者當前head節(jié)點不為空或者waitStatus為SIGNAL(-1) 或者 PROPAGATE(-3),此時需要繼續(xù)喚醒后繼節(jié)點來嘗試獲取資源。 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { // 當前node節(jié)點的后繼節(jié)點 Node s = node.next; //如果后節(jié)點為空或者屬于共享節(jié)點 if (s == null || s.isShared()) // 繼續(xù)嘗試獲取資源 doReleaseShared(); } }
首先將當前節(jié)點設置為head節(jié)點 setHead(node), 其次根據(jù)條件看是否對后繼節(jié)點繼續(xù)喚醒。
獲取資源失敗進行阻塞等待unpark
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { // 獲取前驅節(jié)點的等待狀態(tài) int ws = pred.waitStatus; // 如果等待狀態(tài)已經(jīng)為SIGNAL(表示當前當前節(jié)點的后繼節(jié)點處于等待狀態(tài),如果當前節(jié)點釋放了同步狀態(tài)或者被中斷, 則會喚醒后繼節(jié)點) if (ws == Node.SIGNAL) // 直接返回,表示可以安心的去休息了 return true; // 如果前驅的節(jié)點的狀態(tài) ws > 0(表示該節(jié)點已經(jīng)被取消或者中斷,也就是成無效節(jié)點,需要從同步隊列中取消的) if (ws > 0) { // 循環(huán)往前需尋找,知道尋找到一個有效的安全點(一個等待狀態(tài)<= 0 的節(jié)點,排在它后面) do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); // 注意這一波操作后,獲獎取消的節(jié)點全部變成GC可回收的廢棄鏈。 pred.next = node; } else { //如果前驅正常,那就把前驅的狀態(tài)設置成SIGNAL,告訴它獲取資源后通知自己一下。有可能失敗,人家說不定剛剛釋放完呢! compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } private final boolean parkAndCheckInterrupt() { // 調(diào)用park方法使當前節(jié)點的線程進入waiting LockSupport.park(this); //返回線程中斷狀態(tài) return Thread.interrupted(); }
這兩個方法和獨占式基本相同。
接著看doReleaseShared 這個比較復雜
private void doReleaseShared() { //注意,這里的頭結點已經(jīng)是上面新設定的頭結點了,從這里可以看出,如果propagate=0, //不會進入doReleaseShared方法里面,那就有共享式變成了獨占式 for (;;) { // 死循環(huán)以防在執(zhí)行此操作時添加新節(jié)點:退出條件 h == head Node h = head; // 前提條件,當前的頭節(jié)點不為空,并且不是尾節(jié)點 if (h != null && h != tail) { // 當前頭節(jié)點的等待狀態(tài) int ws = h.waitStatus; if (ws == Node.SIGNAL) { // 如果當前節(jié)點的狀態(tài)為SIGNAL,則利用CAS將其狀態(tài)設置為0(也就是初始狀態(tài)) //這里不直接設為Node.PROPAGATE,是因為unparkSuccessor(h)中,如果ws < 0會設置為0,所以ws先設置為0,再設置為PROPAGATE //這里需要控制并發(fā),因為入口有setHeadAndPropagate跟release兩個,避免兩次unpark if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases 設置失敗,重新循環(huán) // 喚醒后繼節(jié)點 unparkSuccessor(h); } // 如果等待狀態(tài)不為0 則利用CAS將其狀態(tài)設置為PROPAGATE ,以確保在釋放資源時能夠繼續(xù)通知后繼節(jié)點。 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed 如果head 期間發(fā)生了改變,則需要從新循壞 break; } }
private void unparkSuccessor(Node node) { int ws = node.waitStatus; // 在此再次判斷當前頭節(jié)點的的狀態(tài),如果小于0 將設置為0 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); //獲取后繼節(jié)點 Node s = node.next; if (s == null || s.waitStatus > 0) { //如果后繼節(jié)點為空或者等待狀態(tài)大于0 直接放棄。 s = null; for (Node t = tail; t != null && t != node; t = t.prev) // 循環(huán)從尾部往前尋找下一個等待狀態(tài)不大于0的節(jié)點 if (t.waitStatus <= 0) s = t; } // 喚醒該節(jié)點的線程 if (s != null) LockSupport.unpark(s.thread); }
3.2 共享狀態(tài)釋放
最后一步釋放資源就比較簡單了。
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }四、總結
在獲取同步狀態(tài)時,同步器維護一個同步隊列,獲取狀態(tài)失敗的線程會加入到隊列中并進行自旋,出列的(或者停止自旋)的條件時前驅節(jié)點為頭節(jié)點并且成功獲取了同步狀態(tài)。在釋放同步狀態(tài)時,調(diào)用Release方法釋放同步狀態(tài),然后喚醒頭節(jié)點的后繼節(jié)點。
共享式方式在喚醒后繼節(jié)點獲得資源后會判斷當前資源是否還有多余的,如果有會繼續(xù)喚醒下一個節(jié)點。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/74283.html
摘要:本章我們主要聊獨占式即同一時刻只能有一個線程獲取同步狀態(tài),其它獲取同步狀態(tài)失敗的線程則會加入到同步隊列中進行等待。到這獨占式獲取同步和釋放同步狀態(tài)的源碼已經(jīng)分析完了。 一、寫在前面 上篇文章通過ReentrantLock 的加鎖和釋放鎖過程給大家聊了聊AQS架構以及實現(xiàn)原理,具體參見《J.U.C|AQS的原理》。 理解了原理,我們在來看看再來一步一步的聊聊其源碼是如何實現(xiàn)的。 本章給...
摘要:二什么是重入鎖可重入鎖,顧名思義,支持重新進入的鎖,其表示該鎖能支持一個線程對資源的重復加鎖。將由最近成功獲得鎖,并且還沒有釋放該鎖的線程所擁有??梢允褂煤头椒▉頇z查此情況是否發(fā)生。 一、寫在前面 前幾篇我們具體的聊了AQS原理以及底層源碼的實現(xiàn),具體參見 《J.U.C|一文搞懂AQS》《J.U.C|同步隊列(CLH)》《J.U.C|AQS獨占式源碼分析》《J.U.C|AQS共享式源...
摘要:二什么是同步隊列同步隊列一個雙向隊列,隊列中每個節(jié)點等待前驅節(jié)點釋放共享狀態(tài)鎖被喚醒就可以了。三入列操作如上圖了解了同步隊列的結構,我們在分析其入列操作在簡單不過。 一、寫在前面 在上篇我們聊到AQS的原理,具體參見《J.U.C|AQS原理》。 這篇我們來給大家聊聊AQS中核心同步隊列(CLH)。 二、什么是同步隊列(CLH) 同步隊列 一個FIFO雙向隊列,隊列中每個節(jié)點等待前驅...
摘要:造成當前線程在接到信號被中斷或到達指定最后期限之前一直處于等待狀態(tài)。該線程從等待方法返回前必須獲得與相關的鎖。如果線程已經(jīng)獲取了鎖,則將喚醒條件隊列的首節(jié)點。 一、寫在前面 在前幾篇我們聊了 AQS、CLH、ReentrantLock、ReentrantReadWriteLock等的原理以及其源碼解讀,具體參見專欄 《非學無以廣才》 這章我們一起聊聊顯示的Condition 對象。 ...
摘要:所以就有了讀寫鎖。只要沒有,讀取鎖可以由多個線程同時保持。其讀寫鎖為兩個內(nèi)部類都實現(xiàn)了接口。讀寫鎖同樣依賴自定義同步器來實現(xiàn)同步狀態(tài)的,而讀寫狀態(tài)就是其自定義同步器的狀態(tài)。判斷申請寫鎖數(shù)量是否超標超標則直接異常,反之則設置共享狀態(tài)。 一、寫在前面 在上篇我們聊到了可重入鎖(排它鎖)ReentrantLcok ,具體參見《J.U.C|可重入鎖ReentrantLock》 Reentra...
閱讀 1472·2023-04-25 16:31
閱讀 2070·2021-11-24 10:33
閱讀 2767·2021-09-23 11:33
閱讀 2561·2021-09-23 11:31
閱讀 2946·2021-09-08 09:45
閱讀 2362·2021-09-06 15:02
閱讀 2673·2019-08-30 14:21
閱讀 2338·2019-08-30 12:56