一、前言
內部類繼承了AbstractQueuedSynchronize(簡稱AQS),可見,AQS是實現這些類的基石,只要把AQS的原始碼理解透徹了,那些類也就一目瞭然了。廢話少說,我們直接進入主題。
二、AQS的成員變數
前面說了,AQS是ReentrantLock、CountDownLatch等類實現的基石,那麼AQS內部實現了什麼以至於這些類都要依賴於它?
通俗地說就是AQS為這些類提供了一個密碼鎖,這些類擁有了密碼鎖之後就可以自己來設定密碼。原始碼如下:
/** * 同步佇列的頭節點,延遲初始化。 * 除初始化外,只能透過setHead方法進行修改。 * 注意:如果head存在,則保證其waitStatus不為 CANCELLED。 */private transient volatile Node head;/** * 同步佇列的尾節點,延遲初始化。 * 僅透過方法enq新增新的尾節點 */private transient volatile Node tail;/** * 同步狀態 */private volatile int state;/** * 獲取同步狀態 */protected final int getState() { return state;}/** * 設定同步狀態 */protected final void setState(int newState) { state = newState;}/** * CAS方式設定同步狀態 */protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update);}
上面的原始碼中列出了AQS的所有成員變數,只有三個:同步佇列的頭節點引用、同步佇列的尾節點引用以及同步狀態。
這三個成員變數都使用了volatile關鍵字進行修飾,目的就是確保多個執行緒對它的修改是記憶體可見的。
整個類的核心就是同步狀態state,它是一個int型的變數,可以充當密碼鎖,state的具體值可以看做密碼控制密碼的開或關。但是這個鎖的密碼是多少是由各個子類來決定的,
例如ReentrantLock,state等於0表示鎖是開的,state大於0表示鎖是關的,而像Semaphore,state大於0表示鎖是開的,state等於0表示鎖是關的。
三、AQS的同步佇列和條件佇列。
AQS內部其實有兩個排隊區,一個是同步佇列,一個是條件佇列。如上圖所示,同步佇列只有一個,而條件佇列可以有多個。
同步佇列的節點分別持有前驅和後繼節點的引用,而條件佇列的節點只有一個指向後繼節點的引用。圖中的T代表執行緒,每個節點包含一個執行緒,執行緒在獲取鎖失敗後首先會進入同步佇列排隊,要想進入條件佇列,該執行緒必須先獲取鎖才行。下面來看下節點的原始碼:
static final class Node { /** 表示節點(即執行緒)以共享模式持有鎖 */ static final Node SHARED = new Node(); /** 表示節點(即執行緒)以獨佔模式持有鎖 */ static final Node EXCLUSIVE = null; /** 表示執行緒已經取消獲取鎖 */ static final int CANCELLED = 1; /**表示後繼執行緒需要被喚醒 */ static final int SIGNAL = -1; /** 表示執行緒正在條件佇列排隊 */ static final int CONDITION = -2; /** * 表示後繼執行緒可以直接獲取鎖 */ static final int PROPAGATE = -3; /** * Status field, taking on only the values: * SIGNAL: The successor of this node is (or will soon be) * blocked (via park), so the current node must * unpark its successor when it releases or * cancels. To avoid races, acquire methods must * first indicate they need a signal, * then retry the atomic acquire, and then, * on failure, block. * CANCELLED: This node is cancelled due to timeout or interrupt. * Nodes never leave this state. In particular, * a thread with cancelled node never again blocks. * CONDITION: This node is currently on a condition queue. * It will not be used as a sync queue node * until transferred, at which time the status * will be set to 0. (Use of this value here has * nothing to do with the other uses of the * field, but simplifies mechanics.) * PROPAGATE: A releaseShared should be propagated to other * nodes. This is set (for head node only) in * doReleaseShared to ensure propagation * continues, even if other operations have * since intervened. * 0: None of the above * * 節點的等待狀態,預設值是0 */ volatile int waitStatus; /** * 同步佇列的前驅節點 */ volatile Node prev; /** * 同步佇列的後繼節點 */ volatile Node next; /** * 當前節點持有的執行緒引用 */ volatile Thread thread; /** * 條件佇列的後繼節點 */ Node nextWaiter; /** * 返回當前節點是否是共享模式持有鎖 */ final boolean isShared() { return nextWaiter == SHARED; } /** * 返回當前節點的前驅節點 */ final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } // 構造器1,用於初始化頭節點或預設SHARED模式持有鎖 Node() { } // 構造器2 Node(Thread thread, Node mode) { //持有模式是賦值給nextWaiter this.nextWaiter = mode; this.thread = thread; } //構造器3,只在條件佇列中使用 Node(Thread thread, int waitStatus) { this.waitStatus = waitStatus; this.thread = thread; }}
Node是AQS的內部類,代表了同步佇列和條件佇列中的一個節點。Node有很多屬性,比如鎖的持有模式,等待狀態,同步佇列的前驅和後繼節點,條件佇列的後繼節點等等。
四、AQS的獨佔模式和共享模式
如上面的原始碼,持有鎖的模式分為獨佔模式和共享模式,這兩種模式怎麼理解呢?舉個例子,以餐廳就餐為例,現在是用餐高峰,餐廳只有一間包廂是空閒的,獨佔模式的人比較霸道,
他要麼不進包廂就餐,進來了就不允許別人再進來,自己獨佔整個包廂。共享模式的人就沒那麼講究了,他進來坐下吃飯還不算,還熱情的問問後面的人介不介意一起用餐,如果後面的人不介意那麼就不用再排隊,
大家一起用餐,如果後面的人介意,那就只能排隊等待就餐。
五、節點的等待狀態
上面的原始碼中還有一個等待狀態的變數,這個等待狀態又分為CANCELLED,SIGNAL,CONDITION,PROPAGATE四種狀態。怎麼理解這個狀態呢?
還是以餐廳就餐為例,用餐高峰期,餐廳裡都坐滿了人,再來用餐的人就需要等待有空閒的位置才能就餐。餐廳也很貼心,在餐廳外邊為每一位等待就餐的客人準備了一把椅子用於休息。
等待狀態就可以看作是掛在椅子上的牌子,標識當前椅子上客人的等待狀態。這個牌子的狀態不僅自己可以修改,其他人也能修改。
例如當某個客人在排隊中打算放棄了,他就會將自己椅子上的牌子設為CANCELLED,這樣其他客人看到這個狀態就會將它清理出佇列。
又比如當某個客人在椅子上小憩,他怕自己睡過頭了,就會將前面椅子上的牌子改為SIGNAL,因為每個人在離開排隊區之前都會看一下自己椅子上的牌子,如果牌子上的
狀態為SIGnAL,他就會去喚醒排在他後面的人。只有保證前面椅子上的牌子是SIGNAL,這個客人才能安心小憩。
CONDITION狀態表示執行緒在條件佇列中排隊,PROPAGATE狀態是提醒後面的執行緒可以直接獲取鎖,這個狀態只在共享模式使用到。
六、節點入隊
/** * 將節點插入佇列,必要時進行初始化。 */private Node enq(final Node node) { for (;;) { //獲取同步佇列的尾節點 Node t = tail; //如果尾節點為空說明同步佇列還未初始化 if (t == null) { //初始化同步佇列,CAS方式設定同步佇列的頭節點 if (compareAndSetHead(new Node())) tail = head; } else { //1.新插入節點的前驅指向當前尾節點 node.prev = t; //2. CAS方式設定新插入的節點為尾節點 if (compareAndSetTail(t, node)) { //3. 舊的尾節點的後繼指向當前節點(新的尾節點) t.next = node; //返回舊的尾節點 return t; } } }}
從原始碼可知,入隊操作用了一個死迴圈,只有成功將節點插入到同步佇列的尾部,方法才會結束,返回結果是同步佇列舊的尾節點。整個操作過程如下圖:
節點入隊的順序,分為三步:指向尾結點,CAS更改尾結點,將舊尾結點的後繼指向當前結點。在併發環境中這三步操作不一定能保證完成,所以在清空同步佇列所有已取消的結點這一操作中,為了尋找非取消狀態的結點,不是從前向後遍歷而是從後向前遍歷的。還有就是每個結點進入佇列中時它的等待狀態是為0,只有後繼結點的執行緒需要掛起時才會將前面結點的等待狀態改為SIGNAL。這個後續文章會有詳細解讀。