Java併發編程:AbstractQueuedSynchronizer的內部結構


一 前言  

  雖然已經有不少前輩已經分析過AbstractQueuedSynchronizer(簡稱AQS,也叫隊列同步器)類,可是感受那些點始終是別人的,看一遍甚至幾遍終不會印象深入。因此仍是記錄下來印象更深入,還能和你們一塊兒探討(這就是重複造輪子的好處,另外也主要是這篇篇幅太長了,猶豫了很久才決定寫做)。既然有不少前輩都分析過這個類說明它是多麼的重要,下面咱們看下concurrent包的實現示意圖就清楚AQS的所佔有的地位了。html

二 什麼是AQS

  AbstractQueuedSynchronizer,中文簡稱隊列同步器,英文簡稱AQS。它是用來構建鎖或者其餘同步組件的基礎框架,它使用了一個int成員變量表示同步狀態,經過內置的FIFO隊列來完成資源獲取線程的排隊工做。從上面圖能夠看出AQS是實現鎖或任意同步組件的關鍵,經過繼承同步器並實現它的抽象方法來管理同步狀態等。java

三 AQS的內部結構

  我的習慣喜歡先看其內部結構,由於內部結果是一個類實現的核心。通過分析得知:AQS類底層的數據結構是使用雙向鏈表,包括head結點和tail結點,head結點主要用做後續的調度。另外還包含一個單向鏈表,只有當使用Condition時,纔會存在此單向鏈表。而且可能會有多個Condition 鏈表(其中鏈表是隊列的一種具體表現,因此也可稱做隊列)。以下圖:node

四 內部結構源碼解析

3.1 類的繼承關係

  

  一、說明它是一個抽象類,就說明它可能存在抽象方法須要子類去重寫實現(具體有哪些方法須要重寫後續會說明)。面試

  二、它還繼承了AbstractOwnableSynchronizer(簡稱AOS)類能夠設置獨佔資源線程和獲取獨佔資源線程(獨佔鎖會涉及到,AOS的源碼本身能夠進去看看)。性能優化

  另外建議各位多看看類上的註釋,其實還蠻有做用的。bash

3.2 類的內部類

  先分析內部類中的結構再看AQS是怎麼引用它的。下面先看Node.class,主要分析都在註釋上了。數據結構

/**
 * Wait queue node class.
 * 注意看類上的註釋,上面是原註釋的第一行,表示等待隊列節點類(雖然其實是一個雙向鏈表)。
 */
static final class Node {
    /**
     * 總共分爲二者模式:共享和獨佔
     */
    /** 在共享模式中等待的節點 */
    static final Node SHARED = new Node();
    /** 在獨佔模式中等待的節點 */
    static final Node EXCLUSIVE = null;

    /**
     * 下面幾個表示節點狀態,也就是waitStatus所具備可能的值。
     */
    /**
     * 標記線程處於取消狀態
     * 節點進入該狀態就不會變化。
     * /
    static final int CANCELLED =  1;
    /**
     * 標記後繼節點的線程處於等待狀態,須要被取消停放(即被喚醒unpark)。
     * 變化狀況:噹噹前節點的線程若是釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點的線程得以運行。
     */
    static final int SIGNAL    = -1;
    /**
     * 標記線程正在等待條件(Condition),也就是該節點處於等待隊列中。
     * 變化狀況:當其餘線程對Condition調用了signal()方法後,該節點將會從等待隊列中轉移到同步隊列中,加入到同步狀態的獲取中。
     */
    static final int CONDITION = -2;
    /**
     * 表示下一次共享式同步狀態獲取將會無條件的被傳播下去。
     */
    static final int PROPAGATE = -3;

    /**
     * 節點狀態,包含上面四種狀態(另外還有一種初始化狀態0)
     * 特別注意:它是volatile關鍵字修飾的,保證對其線程可見性,可是不保證原子性。
     * 因此更新狀態時,採用CAS方式去更新, 如:compareAndSetWaitStatus
     */
    volatile int waitStatus;

    /**
     * 前驅節點,好比當前節點被取消,那就須要前驅節點和後繼節點來完成鏈接。
     */
    volatile Node prev;

    /**
     * 後繼節點。
     */
    volatile Node next;

    /**
     * 入隊列時的當前線程。
     */
    volatile Thread thread;

    /**
     * 存儲condition隊列中的後繼節點。
     */
    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;
    }
    // 省略三個構造函數
}複製代碼

  總結下:當每一個線程被阻塞時都會封裝成一個Node節點,放入隊列中。每一個節點都包含了當前節點對應的線程、狀態、前置節點引用、後繼節點引用以及下一個等待者。架構

  其中還須要注意的是waitStatus對應的各個狀態表明着什麼意思,另外不清楚volatile關鍵字做用的請前去閱讀下。併發

屬性名稱 描述
int waitStatus 表示節點的狀態。其中包含的狀態有:
  1. CANCELLED,值爲1,表示當前的線程被取消;節點進入該狀態就不會變化。
  2. SIGNAL,值爲-1,表示當前節點的後繼節點包含的線程須要運行,也就是unpark;變化狀況:噹噹前節點的線程若是釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點的線程得以運行。
  3. CONDITION,值爲-2,表示當前節點在等待condition,也就是在condition隊列中;變化狀況:當其餘線程對Condition調用了signal()方法後,該節點將會從等待隊列中轉移到同步隊列中,加入到同步狀態的獲取中。
  4. PROPAGATE,值爲-3,表示當前場景下後續的acquireShared可以得以執行;
  5. 值爲0,表示當前節點在sync隊列中,等待着獲取鎖。
Node prev
前驅節點,好比當前節點被取消,那就須要前驅節點和後繼節點來完成鏈接。複製代碼
Node next
後繼節點。複製代碼
Thread thread
入隊列時的當前線程。複製代碼
Node nextWaiter複製代碼
存儲condition隊列中的後繼節點。
複製代碼

  接下來簡單看看ConditionObject的源碼,後續咱們會單獨分析下這個類的做用。框架

/**
 * 實現Condition接口
 */
public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    /**
     * 條件隊列的第一個節點。
     */
    private transient AbstractQueuedSynchronizer.Node firstWaiter;
    /**
     * 條件隊列的最後一個節點。
     */
    private transient AbstractQueuedSynchronizer.Node lastWaiter;
}複製代碼

  從中能夠看它仍是實現了Condition接口,而Condition接口又定義了什麼規範呢?本身去看:),你會不會發現有點跟Object中的幾個方法相似呢。

3.3 主要內部成員

// 頭結點
    private transient volatile Node head;
    // 尾結點
    private transient volatile Node tail;
    // 同步狀態
    private volatile int state;複製代碼

五 總結

  經過上述分析就很清楚其內部結構是什麼了吧。總結下:

  節點(Node)是成爲sync隊列和condition隊列構建的基礎,在同步器中就包含了sync隊列(Node雙向鏈表)。同步器擁有三個成員變量:sync隊列的頭結點head、sync隊列的尾節點tail和狀態state。對於鎖的獲取,請求造成節點,將其掛載在尾部,而鎖資源的轉移(釋放再獲取)是從頭部開始向後進行。對於同步器維護的狀態state,多個線程對其的獲取將會產生一個鏈式的結構。

在此我向你們推薦一個架構學習交流羣。交流學習羣號:478030634 裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化、分佈式架構等這些成爲架構師必備的知識體系。還能領取免費的學習資源,目前受益良多

你們以爲文章對你仍是有一點點幫助的,你們能夠點擊下方二維碼進行關注。 《Java爛豬皮》 公衆號聊的不只僅是Java技術知識,還有面試等乾貨,後期還有大量架構乾貨。你們一塊兒關注吧!關注爛豬皮,你會了解的更多..............



原文:www.cnblogs.com/yuanfy008/p…

相關文章
相關標籤/搜索