基於ReentrantLock的AQS的源碼分析(獨占、非中斷、不超時部分)

来源:http://www.cnblogs.com/yanbo2016/archive/2016/03/11/5266825.html
-Advertisement-
Play Games

剛剛看完了併發實踐這本書,算是理論具備了,看到了AQS的介紹,再看看源碼,發現要想把併發理解透還是很難得,花了幾個小時細分析了一下把可能出現的場景儘可能的往代碼中去套,還是有些收穫,但是真的很費腦,還是對多線程的理解太淺了,不多說了,直接上代碼吧。 這段代碼不是為跑通,只是把AQS,Reentran


剛剛看完了併發實踐這本書,算是理論具備了,看到了AQS的介紹,再看看源碼,發現要想把併發理解透還是很難得,花了幾個小時細分析了一下把可能出現的場景儘可能的往代碼中去套,還是有些收穫,但是真的很費腦,還是對多線程的理解太淺了,不多說了,直接上代碼吧。

這段代碼不是為跑通,只是把AQS,ReentrantLock中的部分源碼合併到了一起,便於理解。

  1 package com.yb.interview.concurrent;
  2 
  3 
  4 import java.util.concurrent.locks.LockSupport;
  5 
  6 public class AQSSourceStudy {
  7 
  8     abstract static class AQS {
  9         /**
 10          * 這個狀態是有子類來維護的,AQS不會用這個狀態做什麼
 11          */
 12         private volatile int state;
 13         /**
 14          * 隊尾節點
 15          */
 16         private volatile Node tail;
 17         /**
 18          * 可能情況
 19          */
 20         private volatile Node head;
 21         /**
 22          * 獨占線程
 23          */
 24         private Thread exclusiveOwnerThread;
 25 
 26 
 27         /**
 28          * 由子類實現
 29          * 判斷當前線程是否需要排隊
 30          */
 31         abstract boolean tryAcquire(int i);
 32 
 33         public int getState() {
 34             return state;
 35         }
 36 
 37         public void setState(int state) {
 38             this.state = state;
 39         }
 40 
 41         /**
 42          * 主方法
 43          * 可能的情況
 44          * 當前狀態可以直接運行
 45          * 當前狀態要放入隊列里等待
 46          * 狀態->子類獲取
 47          * 過程,儘可能的不要去阻塞,迴圈多次,競爭多次
 48          * 創建節點
 49          * 節點入隊,隊尾
 50          * 判斷新節點的前一個節點的狀態,更新,前一個節點,因為在入隊的過程中每個節點的狀態是動的
 51          * 最後,阻塞當前線程
 52          */
 53         public final void acquire(int arg) {
 54             if (!tryAcquire(arg) &&
 55                     acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 56                 // 中斷狀態傳播
 57                 // 實時或者將來阻塞,拋中斷異常
 58                 selfInterrupt();
 59         }
 60 
 61         /**
 62          * 當有新節點入隊時,迴圈的把新節點關聯到一個有效節點的後面
 63          * 然後,阻塞這個節點的線程(當前線程)
 64          */
 65         private boolean acquireQueued(Node node, int arg) {
 66             boolean failed = true;
 67             try {
 68                 boolean interrupted = false;
 69                 for (; ; ) {
 70                     final Node p = node.predecessor();
 71                     // 新節點的前個節點是頭結點,如果頭結點的線程釋放,新節點接可以直接執行
 72                     // 所有不要著急阻塞,在判斷一次,頭結點釋放沒有,如果頭結點釋放,新節點不阻塞,把新節點設為頭結點
 73                     // 當新節點沒有排隊直接運行了,之後要將節點標記為無效 cancelAcquire
 74                     if (p == head && tryAcquire(arg)) {
 75                         // 想了很久這段代碼發生的情況
 76                         // 這段代碼發生的情況
 77                         // 1.node在入隊列時,有不同的線程在獲得了鎖,且隊列中沒有節點
 78                         // 2.當執行到這裡再次tryAcquire之前,之前釋放了鎖
 79                         // 3.這時hasQueuedPredecessors中的判斷,頭結點的後一個節點,是新建的這個節點,滿足s.thread==Thread.currentThread(不考慮這時有其他線程進入,或者進入無效)
 80                         // 滿足了tryAcquire返回true的情況
 81                         // 將頭結點改為新節點
 82                         /****
 83                          * head          tail
 84                          * |               |
 85                          * |               |
 86                          * ----------    ---------
 87                          * nullNode      newNode
 88                          * ---------     ----------
 89                          * next=newNode  prev=nullNode
 90                          * prev=null     next=null
 91                          * -------       ----------
 92                          *
 93                          * 改完後
 94                          *
 95                          *             head tail
 96                          *               |    |
 97                          *               |    |
 98                          * ---------    ---------
 99                          * nullNode      newNode
100                          * ---------     ---------
101                          * next=newNode  prev=nullNode
102                          * prev=null     next=null
103                          * ---------     ----------
104                          * */
105 
106                         setHead(node);
107                         p.next = null;
108                         failed = false;
109                         return interrupted;
110                     }
111                     // 之前的節點不是正在執行線程的節點,調整位置和狀態再阻塞
112                     // 線上程解除阻塞後,使者節點失效
113                     if (shouldParkAfterFailedAcquire(p, node) &&
114                             parkAndCheckInterrupt())
115                         interrupted = true;
116                 }
117             } finally {
118                 if (failed)
119                     // 節點解除阻塞後,可能是中斷或者超時
120                     // 非unlock的解鎖
121                     cancelAcquire(node);
122             }
123         }
124 
125         private void cancelAcquire(Node node) {
126             if (node == null)
127                 return;
128             node.thread = null;
129             Node pred = node.prev;
130             // 那個空的節點會保證終止
131             while (pred.waitStatus > 0)
132                 // 將節點的prev關聯到最近的有效節點
133                 node.prev = pred = pred.prev;
134             Node predNext = pred.next;
135             // 任何情況都執行的
136             node.waitStatus = Node.CANCELLED;
137 
138             // 如果取消的節點是隊尾節點,並且將前節點設為隊尾節點
139             if (node == tail && compareAndSetTail(node, pred)) {
140                 // cancel的節點和cancel之前的無效節點會移出隊列
141                 compareAndSetNext(pred, predNext, null);
142             } else {
143                 // 如果不是隊尾節點
144                 int ws;
145                 if (pred != head &&
146                         ((ws = pred.waitStatus) == Node.SIGNAL ||
147                                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
148                         pred.thread != null) {
149                     Node next = node.next;
150                     if (next != null && next.waitStatus <= 0)
151                         // prev->node->next  改為 prev->next
152                         compareAndSetNext(pred, predNext, next);
153                 } else {
154                     // 判斷鎖定的狀態
155                     // 如果前節點是頭結點,或者不是SIGNAL狀態並且無法設置為SIGNAL狀態
156                     // 總結,取消一個節點是,要保證這個節點能被釋放,要不通過前節點通知,在鎖鎖,對應release
157                     unparkSuccessor(node);
158                 }
159 
160                 node.next = node; // help GC
161             }
162         }
163 
164         private void unparkSuccessor(Node node) {
165             // 解鎖節點的線程
166             // 當node時頭節點時,是當前獲取線程釋放的炒作
167             // 不是偷節點
168             int ws = node.waitStatus;
169             if (ws < 0)
170                 // 不用再去通知下個節點了,即將釋放node了
171                 compareAndSetWaitStatus(node, ws, 0);
172             Node s = node.next;
173             if (s == null || s.waitStatus > 0) {
174                 s = null;
175                 // 從隊尾向前找到最前有效的節點
176                 for (Node t = tail; t != null && t != node; t = t.prev)
177                     if (t.waitStatus <= 0)
178                         s = t;
179             }
180             if (s != null)
181                 LockSupport.unpark(s.thread);
182 
183         }
184 
185         private void compareAndSetNext(Node pred, Node predNext, Object o) {
186 
187         }
188 
189         private boolean parkAndCheckInterrupt() {
190             // 阻塞
191             LockSupport.park(this);
192             // 當前前程標記中斷
193             return Thread.interrupted();
194         }
195 
196         private boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
197             int ws = pred.waitStatus;
198             // 如果前節點是需要被通知的,前節點正在被阻塞,阻塞當先線程
199             if (ws == Node.SIGNAL)
200                 return true;
201             // 如果前節點是無效的,找到最近的一個有效節點,並關聯,返回,在外部調用方法中會再次調用這個方法
202             if (ws > 0) {
203                 do {
204                     node.prev = pred = pred.prev;
205                 } while (pred.waitStatus > 0);
206                 // 這是個切斷調用鏈的過程
207                 pred.next = node;
208             } else {
209                 // 更新前節點的狀態,釋放時通知新節點
210                 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
211             }
212             return false;
213         }
214 
215         /**
216          * 創建節點
217          * 節點入隊
218          *
219          * @return 新節點
220          */
221         private Node addWaiter(Node mode) {
222             Node node = new Node(Thread.currentThread(), mode);
223             Node pred = tail;
224             // 之前有節點在隊列中
225             if (pred != null) {
226                 node.prev = pred;
227                 // 直接修改隊尾,不成功要進入接下類的迴圈,迴圈中也有類型的判斷,這裡添加會減少一些邏輯(這樣說可能是理解的有偏差)
228                 if (compareAndSetTail(pred, node)) {
229                     pred.next = node;
230                     return node;
231                 }
232             }
233             enq(node);
234             return node;
235         }
236 
237         /**
238          * 節點入隊
239          * 迴圈,直到把新節點放到隊尾,在多線程中這個過程是不確定的
240          */
241         private Node enq(Node node) {
242             for (; ; ) {
243                 Node t = tail;
244                 // Must initialize
245                 // 隊尾沒值,新節點是第一個入隊的節點,創建一個空的節點,頭尾都指向這個空節點
246                 if (t == null) {
247                     if (compareAndSetHead(new Node()))
248                         tail = head;
249                 } else {
250                     node.prev = t;
251                     if (compareAndSetTail(t, node)) {
252                         t.next = node;
253                         return t;
254                     }
255                 }
256             }
257         }
258 
259         /**
260          * 字面理解,是否有已經排隊的線程
261          * 實際意義,有重入鎖的情況,在這裡要考慮到
262          * 沒有節點在排隊的情況,頭結點與未節點是相同的
263          * 判斷重入,當前線程是頭結點的線程.
264          */
265         protected boolean hasQueuedPredecessors() {
266             Node t = tail;
267             Node h = head;
268             Node s;
269             //為什麼是頭結點的線程,而不是exclusiveOwnerThread,因為只有在
270             // 當前隊列里沒有值得時候才回設置獨占線程,如果是通過節點釋放的線
271             // 程還會和節點綁定,不會映射到exclusiveOwnerThread
272             return h != t &&
273                     ((s = h.next) == null || s.thread != Thread.currentThread());
274         }
275 
276         public final boolean release(int arg) {
277             if (tryRelease(arg)) {
278                 Node h = head;
279                 // 在獨占鎖的時候,waitStatus只能為0 -1 -2 -3
280                 // 這個裡不為0代表頭節點是空節點
281                 // 空節點不需要釋放
282                 // 頭節點是釋放鎖的時候,最先被考慮的
283                 if (h != null && h.waitStatus != 0)
284                     unparkSuccessor(h);
285                 return true;
286             }
287             return false;
288         }
289 
290         protected abstract boolean tryRelease(int arg);
291 
292 
293         public void setHead(Node head) {
294             this.head = head;
295         }
296 
297         private boolean compareAndSetHead(Node node) {
298             return (true || false);
299         }
300 
301         private boolean compareAndSetTail(Node pred, Node node) {
302             return (true || false);
303         }
304 
305         protected void selfInterrupt() {
306             Thread.currentThread().interrupt();
307         }
308 
309 
310         /**
311          * CAS更新隊列狀態,CAS的問題在其他的機會介紹
312          */
313         boolean compareAndSetState(int o, int n) {
314             return (false || true);
315         }
316 
317         /**
318          * 獨占線程標記改為指定線程
319          */
320         void setExclusiveOwnerThread(Thread t) {
321             exclusiveOwnerThread = t;
322         }
323 
324         /**
325          * 返回獨占線程
326          */
327         Thread getExclusiveOwnerThread() {
328             return exclusiveOwnerThread;
329         }
330 
331         // 修改節點的狀態
332         private boolean compareAndSetWaitStatus(Node pred, int ws, int signal) {
333             return (true || false);
334         }
335 
336         static class Node {
337 
338             public int waitStatus;
339 
340             Node() {
341             }
342 
343             /**
344              * @param thread
345              * @param mode   SHARED or  EXCLUSIVE
346              */
347             Node(Thread thread, Node mode) {
348                 this.thread = Thread.currentThread();
349                 this.mode = mode;
350             }
351 
352             // 共用模式標記
353             static final Node SHARED = new Node();
354             // 獨占模式標記
355             static final Node EXCLUSIVE = null;
356 
357             // 節點被取消,因為超時或者中斷
358             static final int CANCELLED = 1;
359             // next被阻塞,當節點釋放時,notice next
360             static final int SIGNAL = -1;
361             // 在條件隊列中,等待某個條件被阻塞
362             static final int CONDITION = -2;
363             // 節點在共用模式下,可以傳播鎖
364             static final int PROPAGATE = -3;
365 
366             volatile Node next;
367             volatile Node prev;
368             Node mode;
369 
370             public Thread thread;
371 
372             public Node predecessor() {
373                 Node p = prev;
374                 if (p == null)
375                     throw new NullPointerException();
376                 else
377                     return p;
378             }
379         }
380 
381 
382     }
383 
384     /**
385      * 這是一個獨占鎖的實現,從ReentrantLock中粘貼出來的部分代碼
386      */
387     class SYC extends AQS {
388 
389         public void lock() {
390             acquire(1);
391         }
392 
393         public void unlock() {
394             release(1);
395         }
396 
397         protected final boolean tryAcquire(int acquires) {
398             final Thread current = Thread.currentThread();
399             int c = getState();
400             // 如果當前的狀態
401             if (c == 0) {
402                 if (!hasQueuedPredecessors() &&
403                         compareAndSetState(0, acquires)) {
404                     setExclusiveOwnerThread(current);
405                     return true;
406                 }
407             } else if (current == getExclusiveOwnerThread()) {
408                 int nextc = c + acquires;
409                 if (nextc < 0)
410                     throw new Error("Maximum lock count exceeded");
411                 setState(nextc);
412                 return true;
413             }
414             return false;
415         }
416 
417         protected final boolean tryRelease(int releases) {
418             int c = getState() - releases;
419             if (Thread.currentThread() != getExclusiveOwnerThread())
420                 throw new IllegalMonitorStateException();
421             boolean free = false;
422             if (c == 0) {
423                 free = true;
424                 setExclusiveOwnerThread(null);
425             }
426             setState(c);
427             return free;
428         }
429 
430 
431     }
432 }

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 葡萄城近日與微軟公司達成合作,將Wijmo 產品線的HTML5和JaveScript 控制項融合到微軟Dynamics CRMOnline 2016版中。
  • 隨手記記 先定義下標誌枚舉: 在項目的model文件夾下新建一個IsEnums.cs類 [Flags] public enum ABC {a=1,b=2,c=4, } 然後在HomeController.cs類中引用下model, 用標誌枚舉的好處就是可以進行自由組合,而標誌枚舉里定義每個都是2的N
  • 規則引擎由推理引擎發展而來,是一種嵌入在應用程式中的組件,實現了將業務決策從應用程式代碼中分離出來,並使用預定義的語義模塊編寫業務決策。接受數據輸入,解釋業務規則,並根據業務規則做出業務決策。比較常見的業務規則引擎有Drools、VisualRules 和iLog。這裡介紹另外一個C#開源工具Rul
  • 1.設置百分比顯示而且是自適應。 2. meta標簽設置 ios:正確設置 <meta name="viewport" content="width=device-width;" /> :錯誤設置 <meta name="viewport" content="width=device-width"
  • 本文介紹在不使用PIL的情況下,使用Python保存截屏並保存屏幕截圖到.bmp文件。通過ctypes庫使用C指針來對記憶體進行操作。
  • Atitit.編程語言and 自然語言的比較and 編程語言未來的發展 1. 單詞的間隔靠空格,編程的單詞的間隔靠分界符..1 2. 語句分界符:自然語言使用逗號,編程語言使用分號1 3. 換行1 4. 段落and fun method2 5. 上下文相關2 6. 操作泛型化2 7. 動詞和名詞之間
  • 作者: "@gzdaijie" 本文為作者原創,轉載請註明出處:http://www.cnblogs.com/gzdaijie/p/5267166.html Java Web應用開發時常使用Gradle來進行項目管理,可以十分便利地解決包依賴等問題。war插件的出現,讓項目部署成為一個複製粘貼的過程
  • 建立映射關係 首先變數表應該採取一種將變數名對應到變數的方法,這種方法大致兩種,一種是將變數名parse時hash成數字,一種是直接建立string value的map。 + int |速度快|動態性弱,無法實現諸如getvar("abc")的功能 + string|速度慢|動態性強 其次選擇數據結
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...