天天看點

【Java并發程式設計實戰】----- AQS(四):CLH同步隊列

在【Java并發程式設計實戰】—–“J.U.C”:CLH隊列鎖提過,AQS裡面的CLH隊列是CLH同步鎖的一種變形。其主要從兩方面進行了改造:節點的結構與節點等待機制。在結構上引入了頭結點和尾節點,他們分别指向隊列的頭和尾,嘗試擷取鎖、入隊列、釋放鎖等實作都與頭尾節點相關,并且每個節點都引入前驅節點和後後續節點的引用;在等待機制上由原來的自旋改成阻塞喚醒。其結構如下:

【Java并發程式設計實戰】----- AQS(四):CLH同步隊列

知道其結構了,我們再看看他的實作。線上程擷取鎖時會調用AQS的acquire()方法,該方法第一次嘗試擷取鎖如果失敗,會将該線程加入到CLH隊列中:

addWaiter:

這是addWaiter()的實作,在厘清這段代碼之前我們要先看一個更重要的東東,Node,CLH隊列的節點。其源碼如下:

在這個源代碼中有三個值(CANCELLED、SIGNAL、CONDITION)要特别注意,前面提到過CLH隊列的節點都有一個狀态位,該狀态位與線程狀态密切相關:

CANCELLED =  1:因為逾時或者中斷,節點會被設定為取消狀态,被取消的節點時不會參與到競争中的,他會一直保持取消狀态不會轉變為其他狀态;

SIGNAL    = -1:其後繼節點已經被阻塞了,到時需要進行喚醒操作;

CONDITION = -2:表示這個結點在條件隊列中,因為等待某個條件而被阻塞;

0:建立節點一般都為0。

線上程嘗試擷取鎖的時候,如果失敗了需要将該線程加入到CLH隊列,入列中的主要流程是:tail執行建立node,然後将node的後繼節點指向舊tail值。注意在這個過程中有一個CAS操作,采用自旋方式直到成功為止。其代碼如下:

其實這段代碼在enq()方法中存在。

當線程是否鎖時,需要進行“出列”,出列的主要工作則是喚醒其後繼節點(一般來說就是head節點),讓所有線程有序地進行下去:

線程因為逾時或者中斷涉及到取消的操作,如果某個節點被取消了,那個該節點就不會參與到鎖競争當中,它會等待GC回收。取消的主要過程是将取消狀态的節點移除掉,移除的過程還是比較簡單的。先将其狀态設定為CANCELLED,然後将其前驅節點的pred執行其後繼節點,當然這個過程仍然會是一個CAS操作:

我們了解了AQS的CLH隊列相比原始的CLH隊列鎖,它采用了一種變形操作,将自旋機制改為阻塞機制。目前線程将首先檢測是否為頭結點且嘗試擷取鎖,如果目前節點為頭結點并成功擷取鎖則直接傳回,目前線程不進入阻塞,否則将目前線程阻塞:

參考

1、Java并發架構——AQS阻塞隊列管理(二)

2、Java并發架構——AQS阻塞隊列管理(三)

PS:如果你覺得文章對你有所幫助,别忘了推薦或者分享,因為有你的支援,才是我續寫下篇的動力和源泉!

作者:chenssy。一個專注于【死磕 Java】系列創作的男人

出處:https://www.cnblogs.com/chenssy/p/5087652.html

作者個人網站:https://www.cmsblogs.com/。專注于 Java 優質系列文章分享,提供一站式 Java 學習資料

目前死磕系列包括:

    1. 【死磕 Java 并發】:https://www.cmsblogs.com/category/1391296887813967872(已完成)

    2.【死磕 Spring 之 IOC】:https://www.cmsblogs.com/category/1391374860344758272(已完成)

    3.【死磕 Redis】:https://www.cmsblogs.com/category/1391389927996002304(已完成)

    4.【死磕 Java 基礎】:https://www.cmsblogs.com/category/1411518540095295488

    5.【死磕 NIO】:https://www.cmsblogs.com/article/1435620402348036096

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。