摘要:其二如果返回值等于表示當前線程獲取共享鎖成功,但它后續的線程是無法繼續獲取的,也就是不需要把它后面等待的節點喚醒。
在了解了AQS獨占鎖模式以后,接下來再來看看共享鎖的實現原理。
原文地址:http://www.jianshu.com/p/1161...
搞清楚AQS獨占鎖的實現原理之后,再看共享鎖的實現原理就會輕松很多。兩種鎖模式之間很多通用的地方本文只會簡單說明一下,就不在贅述了,具體細節可以參考我的上篇文章深入淺出AQS之獨占鎖模式
一、執行過程概述獲取鎖的過程:
當線程調用acquireShared()申請獲取鎖資源時,如果成功,則進入臨界區。
當獲取鎖失敗時,則創建一個共享類型的節點并進入一個FIFO等待隊列,然后被掛起等待喚醒。
當隊列中的等待線程被喚醒以后就重新嘗試獲取鎖資源,如果成功則喚醒后面還在等待的共享節點并把該喚醒事件傳遞下去,即會依次喚醒在該節點后面的所有共享節點,然后進入臨界區,否則繼續掛起等待。
釋放鎖過程:
當線程調用releaseShared()進行鎖資源釋放時,如果釋放成功,則喚醒隊列中等待的節點,如果有的話。
二、源碼深入分析基于上面所說的共享鎖執行流程,我們接下來看下源碼實現邏輯:
首先來看下獲取鎖的方法acquireShared(),如下
public final void acquireShared(int arg) { //嘗試獲取共享鎖,返回值小于0表示獲取失敗 if (tryAcquireShared(arg) < 0) //執行獲取鎖失敗以后的方法 doAcquireShared(arg); }
這里tryAcquireShared()方法是留給用戶去實現具體的獲取鎖邏輯的。關于該方法的實現有兩點需要特別說明:
一、該方法必須自己檢查當前上下文是否支持獲取共享鎖,如果支持再進行獲取。
二、該方法返回值是個重點。其一、由上面的源碼片段可以看出返回值小于0表示獲取鎖失敗,需要進入等待隊列。其二、如果返回值等于0表示當前線程獲取共享鎖成功,但它后續的線程是無法繼續獲取的,也就是不需要把它后面等待的節點喚醒。最后、如果返回值大于0,表示當前線程獲取共享鎖成功且它后續等待的節點也有可能繼續獲取共享鎖成功,也就是說此時需要把后續節點喚醒讓它們去嘗試獲取共享鎖。
有了上面的約定,我們再來看下doAcquireShared方法的實現:
//參數不多說,就是傳給acquireShared()的參數 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); //注意上面說的, 等于0表示不用喚醒后繼節點,大于0需要 if (r >= 0) { //這里是重點,獲取到鎖以后的喚醒操作,后面詳細說 setHeadAndPropagate(node, r); p.next = null; //如果是因為中斷醒來則設置中斷標記位 if (interrupted) selfInterrupt(); failed = false; return; } } //掛起邏輯跟獨占鎖一樣,不再贅述 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { //獲取失敗的取消邏輯跟獨占鎖一樣,不再贅述 if (failed) cancelAcquire(node); } }
獨占鎖模式獲取成功以后設置頭結點然后返回中斷狀態,結束流程。而共享鎖模式獲取成功以后,調用了setHeadAndPropagate方法,從方法名就可以看出除了設置新的頭結點以外還有一個傳遞動作,一起看下代碼:
//兩個入參,一個是當前成功獲取共享鎖的節點,一個就是tryAcquireShared方法的返回值,注意上面說的,它可能大于0也可能等于0 private void setHeadAndPropagate(Node node, int propagate) { Node h = head; //記錄當前頭節點 //設置新的頭節點,即把當前獲取到鎖的節點設置為頭節點 //注:這里是獲取到鎖之后的操作,不需要并發控制 setHead(node); //這里意思有兩種情況是需要執行喚醒操作 //1.propagate > 0 表示調用方指明了后繼節點需要被喚醒 //2.頭節點后面的節點需要被喚醒(waitStatus<0),不論是老的頭結點還是新的頭結點 if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) { Node s = node.next; //如果當前節點的后繼節點是共享類型獲取沒有后繼節點,則進行喚醒 //這里可以理解為除非明確指明不需要喚醒(后繼等待節點是獨占類型),否則都要喚醒 if (s == null || s.isShared()) //后面詳細說 doReleaseShared(); } } private void setHead(Node node) { head = node; node.thread = null; node.prev = null; }
最終的喚醒操作也很復雜,專門拿出來分析一下:
注:這個喚醒操作在releaseShare()方法里也會調用。
private void doReleaseShared() { for (;;) { //喚醒操作由頭結點開始,注意這里的頭節點已經是上面新設置的頭結點了 //其實就是喚醒上面新獲取到共享鎖的節點的后繼節點 Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; //表示后繼節點需要被喚醒 if (ws == Node.SIGNAL) { //這里需要控制并發,因為入口有setHeadAndPropagate跟release兩個,避免兩次unpark if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; //執行喚醒操作 unparkSuccessor(h); } //如果后繼節點暫時不需要喚醒,則把當前節點狀態設置為PROPAGATE確保以后可以傳遞下去 else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; } //如果頭結點沒有發生變化,表示設置完成,退出循環 //如果頭結點發生變化,比如說其他線程獲取到了鎖,為了使自己的喚醒動作可以傳遞,必須進行重試 if (h == head) break; } }
接下來看下釋放共享鎖的過程:
public final boolean releaseShared(int arg) { //嘗試釋放共享鎖 if (tryReleaseShared(arg)) { //喚醒過程,詳情見上面分析 doReleaseShared(); return true; } return false; }
注:上面的setHeadAndPropagate()方法表示等待隊列中的線程成功獲取到共享鎖,這時候它需要喚醒它后面的共享節點(如果有),但是當通過releaseShared()方法去釋放一個共享鎖的時候,接下來等待獨占鎖跟共享鎖的線程都可以被喚醒進行嘗試獲取。
三、總結跟獨占鎖相比,共享鎖的主要特征在于當一個在等待隊列中的共享節點成功獲取到鎖以后(它獲取到的是共享鎖),既然是共享,那它必須要依次喚醒后面所有可以跟它一起共享當前鎖資源的節點,毫無疑問,這些節點必須也是在等待共享鎖(這是大前提,如果等待的是獨占鎖,那前面已經有一個共享節點獲取鎖了,它肯定是獲取不到的)。當共享鎖被釋放的時候,可以用讀寫鎖為例進行思考,當一個讀鎖被釋放,此時不論是讀鎖還是寫鎖都是可以競爭資源的。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70609.html
摘要:原文地址深入淺出之獨占鎖模式深入淺出之共享鎖模式深入淺出之條件隊列前面三篇文章如果之前沒有基礎的話看起來會比較吃力,這篇文章說明一下的基礎知識,方便快速了解。當前節點由于超時或者中斷被取消,節點進入這個狀態以后將保持不變。 之前分析了AQS中的獨占鎖,共享鎖,條件隊列三大模塊,現在從結構上來看看AQS各個組件的情況。 原文地址:http://www.jianshu.com/p/49b8...
摘要:從上面的代碼可以看出,條件隊列是建立在鎖基礎上的,而且必須是獨占鎖原因后面會通過源碼分析。明天就是國慶長假了,我自己也計劃出國玩一趟,散散心。提前祝廣大朋友國慶快樂。 相比于獨占鎖跟共享鎖,AbstractQueuedSynchronizer中的條件隊列可能被關注的并不是很多,但它在阻塞隊列的實現里起著至關重要的作用,同時如果想全面了解AQS,條件隊列也是必須要學習的。 原文地址:ht...
摘要:獲取鎖的過程當線程調用申請獲取鎖資源,如果成功,則進入臨界區。如果隊列中有其他等待鎖資源的線程需要喚醒,則喚醒隊列中的第一個等待節點先入先出。釋放鎖時,如果隊列中有等待的線程就進行喚醒。 每一個Java工程師應該都或多或少了解過AQS,我自己也是前前后后,反反復復研究了很久,看了忘,忘了再看,每次都有不一樣的體會。這次趁著寫博客,打算重新拿出來系統的研究下它的源碼,總結成文章,便于以后...
摘要:鎖與很好的隔離使用者與實現者所需要關注的領域。那么這個就是包裝線程并且放入到隊列的過程實現的方法。也證實了就是獲取鎖的線程的節點。如果發生異常取消請求,也就是將當前節點重隊列中移除。 前言 自從JDK1.5后,jdk新增一個并發工具包java.util.concurrent,提供了一系列的并發工具類。而今天我們需要學習的是java.util.concurrent.lock也就是它下面的...
摘要:作用是存儲獲取鎖失敗的阻塞線程。獨占模式下,鎖是線程獨占的,而共享模式下,鎖是可以被多個線程占用的。等方法就是讓線程阻塞加入隊列喚醒線程等。該方法其實就是自旋嘗試獲取鎖或阻塞線程子類實現決定。 AQS,全稱AbstractQueuedSynchronizer,是Concurrent包鎖的核心,沒有AQS就沒有Java的Concurrent包。它到底是個什么,我們來看看源碼的第一段注解是...
閱讀 1894·2021-11-24 11:16
閱讀 3257·2021-09-10 10:51
閱讀 3180·2021-08-03 14:03
閱讀 1261·2019-08-29 17:03
閱讀 3238·2019-08-29 12:36
閱讀 2219·2019-08-26 14:06
閱讀 493·2019-08-23 16:32
閱讀 2662·2019-08-23 13:42