微信公衆號「後端進階」,專一後端技術分享:Java、Golang、WEB框架、分佈式中間件、服務治理等等。
老司機傾囊相授,帶你一路進階,來不及解釋了快上車!java
我在Java併發之AQS源碼分析(一)這篇文章中,從源碼的角度深度剖析了 AQS 獨佔鎖模式下的獲取鎖與釋放鎖的邏輯,若是你把這部分搞明白了,再看共享鎖的實現原理,思路就會清晰不少。下面咱們繼續從源碼中窺探共享鎖的實現原理。node
public final void acquireShared(int arg) { // 嘗試獲取共享鎖,小於0表示獲取失敗 if (tryAcquireShared(arg) < 0) // 執行獲取鎖失敗的邏輯 doAcquireShared(arg); }
這裏的 tryAcquireShared 方法是留給實現方去實現獲取鎖的具體邏輯的,咱們主要看 doAcquireShared 方法的實現邏輯:後端
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 if (interrupted) selfInterrupt(); failed = false; return; } } // 與獨佔鎖相同的掛起邏輯 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
看到上面的代碼,是否是有一種熟悉的感受,一樣是採用了自旋機制,在線程掛起以前,不斷地循環嘗試獲取鎖,不一樣的是,一旦獲取共享鎖,會調用 setHeadAndPropagate 方法同時喚醒後繼節點,實現共享模式,下面是喚醒後繼節點代碼邏輯:安全
private void setHeadAndPropagate(Node node, int propagate) { // 頭節點 Node h = head; // 設置當前節點爲新的頭節點 // 這裏不須要加鎖操做,由於獲取共享鎖後,會從FIFO隊列中依次喚醒隊列,並不會產生併發安全問題 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()) doReleaseShared(); } }
該方法主要作了兩個重要的步驟:微信
public final boolean releaseShared(int arg) { // 由用戶自行實現釋放鎖條件 if (tryReleaseShared(arg)) { // 執行釋放鎖 doReleaseShared(); return true; } return false; }
下面是釋放鎖邏輯:併發
private void doReleaseShared() { for (;;) { // 從頭節點開始執行喚醒操做 // 這裏須要注意,若是從setHeadAndPropagate方法調用該方法,那麼這裏的head是新的頭節點 Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; //表示後繼節點須要被喚醒 if (ws == Node.SIGNAL) { // 初始化節點狀態 //這裏須要CAS原子操做,由於setHeadAndPropagate和releaseShared這兩個方法都會頂用doReleaseShared,避免屢次unpark喚醒操做 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) // 若是初始化節點狀態失敗,繼續循環執行 continue; // loop to recheck cases // 執行喚醒操做 unparkSuccessor(h); } //若是後繼節點暫時不須要喚醒,那麼當前頭節點狀態更新爲PROPAGATE,確保後續能夠傳遞給後繼節點 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } // 若是在喚醒的過程當中頭節點沒有更改,退出循環 // 這裏防止其它線程又設置了頭節點,說明其它線程獲取了共享鎖,會繼續循環操做 if (h == head) // loop if head changed break; } }
共享鎖的釋放鎖邏輯比獨佔鎖的釋放鎖邏輯稍微複雜,緣由是共享鎖須要釋放隊列中全部共享類型的節點,所以須要循環操做,因爲釋放鎖過程當中會涉及多個地方修改節點狀態,此時須要 CAS 原子操做來併發安全。框架
獲取共享鎖流程圖:分佈式
更獨佔鎖相比,從流程圖也可看出,共享鎖的主要特徵是當有一個線程獲取到鎖以後,那麼它就會依次喚醒等待隊列中能夠跟它共享的節點,固然這些節點也是共享鎖類型。oop