0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

AQS是什么

科技綠洲 ? 來源:Java技術(shù)指北 ? 作者:Java技術(shù)指北 ? 2023-10-13 14:54 ? 次閱讀

那什么是管程呢?所謂管程,就是 管理共享變量以及對(duì)共享變量操作的過程 ,其有三種模型,分別為 Hasen 模型、Hoare 模型和 MESA 模型。目前應(yīng)用最廣泛的是MESA模型,而JAVA采用的也是這種MESA模型(其模型圖如下圖所示):

圖片

可能這個(gè)圖大家現(xiàn)在還看不太明白,沒關(guān)系,暫時(shí)留個(gè)印象,當(dāng)看完指北君AQS系列文章以后,你再回過頭來看這個(gè)圖,肯定秒懂!

Java中的synchronized關(guān)鍵字就是其管程的具體實(shí)現(xiàn),當(dāng)然,今天所要聊的AQS同樣也是。AQS是Java并發(fā)編程的基礎(chǔ),只要掌握它,java.util .concurrent工具包下的大部分工具類源碼你都能在10分鐘內(nèi)看懂,連源碼都懂了,還怕不知道怎么用嗎?!

所以,針對(duì)AQS,指北君將用三篇文章來講解:

第一篇即本篇,我會(huì)將AQS做個(gè)整體的介紹,并將其基礎(chǔ)總結(jié)成了三把斧頭,有了這三把斧頭,你就能快速斬獲AQS

第二篇我們則講AQS如何通過鎖機(jī)制解決互斥問題

第三篇?jiǎng)t是AQS如何通過條件變量來解決線程通信協(xié)作問題

好了,現(xiàn)在隨著指北君開始第一篇的學(xué)習(xí)吧

AQS是什么

好了,本文的正篇正式開始。前言說了半天AQS,AQS到底是什么呢?AQS全稱為AbstractQueuedSynchronizer,翻譯成中文即:抽象隊(duì)列同步器,它是java.util .concurrent工具包下的抽象類,也是一個(gè)模板類(設(shè)計(jì)模式中的模板模式可以了解一波),你也可以理解為為開發(fā)人員提供的一種同步框架,它已經(jīng)幫我們實(shí)現(xiàn)了大部分公共通用邏輯,如線程入隊(duì)、出隊(duì),阻塞、喚醒等,我們只需要根據(jù)我們自己的需求,實(shí)現(xiàn)一些特定的方法,這些方法也叫鉤子方法,比如下面幾個(gè)方法:

  1. tryAcquire:獨(dú)占模式下獲取同步狀態(tài)
  2. tryRelease:獨(dú)占模式下釋放同步狀態(tài)
  3. tryAcquireShared:共享模式下獲取同步狀態(tài)
  4. tryReleaseShared:共享模式下釋放同步狀態(tài)
  5. isHeldExclusively:獨(dú)占模式下,查看當(dāng)前線程是否獲取同步狀態(tài)

AQS針對(duì)互斥,提供了兩種模式,即獨(dú)占模式和共享模式。獨(dú)占模式只允許一個(gè)線程拿到鎖去操作共享資源,而共享鎖則有多把鎖,允許多個(gè)線程同時(shí)操作共享資源,這個(gè)第二篇會(huì)詳細(xì)講解,在這之前我們需要先了解AQS的三板斧,這三個(gè)是了解AQS的基礎(chǔ):

  1. 狀態(tài):AQS中的所有邏輯都是依據(jù)狀態(tài)state來進(jìn)行的,所以它是整個(gè)類的和興。它是被關(guān)鍵字volatile修飾的,保證其可見性和部分有序性
  2. 隊(duì)列:一共有兩種隊(duì)列,同步隊(duì)列和條件隊(duì)列。當(dāng)線程的請(qǐng)求在短時(shí)間內(nèi)得不到滿足時(shí),線程會(huì)被包裝成某種類型的數(shù)據(jù)結(jié)構(gòu)放入隊(duì)列中,當(dāng)條件滿足時(shí)則會(huì)拿出隊(duì)列。
  3. CAS:由Unsafe工具類來實(shí)現(xiàn)的,其操作具有原子性,AQS通過CAS和volatile來保證狀態(tài)的線程安全

第一板斧:狀態(tài)

private volatile int state;

狀態(tài)是被volatile修飾的int類型變量,它的值代表著鎖還剩多少。在獨(dú)占鎖模式下,只有一把鎖,則state只有0和1兩個(gè)值,1代表鎖沒被其他線程占有,目前可獲??;0則代表鎖已經(jīng)被其他線程占有了。在共享鎖模式下,state最大值就是鎖的數(shù)量。

后面我們對(duì)state的修改都是通過CAS操作,所以是線程安全的。

第二板斧:隊(duì)列

AQS中存在兩種隊(duì)列,一種叫同步隊(duì)列,它是雙向鏈表,其獲取鎖失敗的線程會(huì)被包裝成Node放入同步隊(duì)列中;另一種叫條件隊(duì)列,它是單向鏈表,線程可以調(diào)用等待方法來把自己放入條件隊(duì)列,或者調(diào)用喚醒方法把條件隊(duì)列的其他被包裝成Node的線程移到同步隊(duì)列中。這個(gè)大家如果此時(shí)看不太懂沒關(guān)系,第三篇文章會(huì)詳細(xì)介紹這個(gè)等待喚醒機(jī)制。我們來看看線程包裝成Node的Node類:

static final class Node {
    
   // nextWaiter屬性的具體值
   static final Node SHARED = new Node();
   static final Node EXCLUSIVE = null;
 
   // 鎖的狀態(tài)
   static final int CANCELLED =  1;
   static final int SIGNAL    = -1;
   static final int CONDITION = -2;
   static final int PROPAGATE = -3;
  
   // 線程所處的等待鎖的狀態(tài):CANCELLED,SIGNAL,CONDITION,PROPAGATE,初始化時(shí),該值為0
   volatile int waitStatus;
 
   // 雙向鏈表前指針和后指針
   volatile Node prev;
   volatile Node next;
  
   // 表示當(dāng)前Node所代表的Thread
   volatile Thread thread;
 
   // 如果此屬性為EXCLUSIVE,則為獨(dú)占模式;為SHARED,則為共享模式
   Node nextWaiter;
 
   // 判斷是否是共享模式
   final boolean isShared() {
       return nextWaiter == SHARED;
   }
 
   // 獲取鏈表的頭個(gè)節(jié)點(diǎn)
   final Node predecessor() throws NullPointerException {
       Node p = prev;
       if (p == null)
           throw new NullPointerException();
       else
           return p;
   }
   
   // 構(gòu)造函數(shù),一般用在創(chuàng)建head頭結(jié)點(diǎn)時(shí)使用
   Node() {    
   }
 
   // 構(gòu)造函數(shù),在addWaiter方法時(shí)使用
   Node(Thread thread, Node mode) {     
       this.nextWaiter = mode;
       this.thread = thread;
   }
  
   // 構(gòu)造函數(shù),在Condition中使用
   Node(Thread thread, int waitStatus) { 
       this.waitStatus = waitStatus;
       this.thread = thread;
   }
}

指北君已經(jīng)在源碼上做了詳細(xì)的注釋,但這幾個(gè)關(guān)鍵的屬性上還是提出來單獨(dú)看看。

下面四個(gè)屬性是和節(jié)點(diǎn)相關(guān)的:

  1. prev:雙向鏈表的前驅(qū)節(jié)點(diǎn)
  2. next:雙向鏈表的后繼節(jié)點(diǎn)
  3. thread:節(jié)點(diǎn)所代表的線程
  4. waitStatus:該節(jié)點(diǎn)線程所處的狀態(tài),即等待鎖的狀態(tài)

下面四個(gè)屬性是waitStatus的具體狀態(tài):

  1. CANCELLED:此節(jié)點(diǎn)的線程被取消了
  2. SIGNAL:此節(jié)點(diǎn)的后繼節(jié)點(diǎn)線程被掛起,需要被喚醒
  3. CONDITION:此節(jié)點(diǎn)的線程在等待信號(hào),也表明當(dāng)前節(jié)點(diǎn)不在同步隊(duì)列中,而在條件隊(duì)列中
  4. PROPAGATE:此節(jié)點(diǎn)下一個(gè)acquireShared應(yīng)該無條件傳播

關(guān)于waitStatus有幾個(gè)點(diǎn)需要注意下:

  1. waitStatus除了上面四個(gè)狀態(tài),還有一個(gè)隱式的狀態(tài)為0,即在Node初始化的時(shí)候
  2. 在獨(dú)占鎖模式下,只會(huì)有到狀態(tài)CANCELLED和SIGNAL。需要特別注意的是,SIGNAL它代表的不是自己線程的狀態(tài),而是它后繼節(jié)點(diǎn)的狀態(tài),當(dāng)一個(gè)節(jié)點(diǎn)waitStatus為SIGNAL時(shí),意味著此節(jié)點(diǎn)的后繼節(jié)點(diǎn)被掛起,當(dāng)此節(jié)點(diǎn)釋放鎖或者被取消拿鎖,應(yīng)該要喚醒后繼節(jié)點(diǎn)
  3. 在共享鎖模式下,只會(huì)用到狀態(tài)CANCELLED和PROPAGATE

第三板斧:CAS

CAS又叫比較交換操作,它是Unsafe類中compareAndSwapXXX方法,我通過下面的例子做個(gè)簡單的介紹:

unsafe.compareAndSwapObject(this, tailOffset, expect, update);

CAS執(zhí)行時(shí),會(huì)拿地址tailOffset上的值與expect做比較,如果相同,則會(huì)將地址上的值更新為update,并返回true,否則直接返回false。

了解了CAS的基本例子后,我們看下AQS中CAS相關(guān)的代碼:

private static final Unsafe unsafe = Unsafe.getUnsafe();
// state、head、tail,waitStatus、next的偏移量
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;

// 靜態(tài)代碼塊,初始化五個(gè)偏移量
static {
   try {
       stateOffset = unsafe.objectFieldOffset
           (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
       headOffset = unsafe.objectFieldOffset
           (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
       tailOffset = unsafe.objectFieldOffset
           (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
       waitStatusOffset = unsafe.objectFieldOffset
           (Node.class.getDeclaredField("waitStatus"));
       nextOffset = unsafe.objectFieldOffset
           (Node.class.getDeclaredField("next"));
 
   } catch (Exception ex) { throw new Error(ex); }
}
 
// 下面5個(gè)方法都是CAS操作了
// cas操作 state
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

// cas操作 head
private final boolean compareAndSetHead(Node update) {
   return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
 
// cas操作 tail
private final boolean compareAndSetTail(Node expect, Node update) {
   return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
 
// cas操作 waitStatus
private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
   return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
}

// cas操作 nextOffset
private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
   return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}

我們說第二板斧的時(shí)候說過,其五個(gè)屬性state、head、tail,waitStatus、next都是被volatile修飾的,所以CAS對(duì)其操作能保證其線程安全,因此也可以猜到這些屬性肯定是多線程爭著修改的目標(biāo)。靜態(tài)塊里則是對(duì)這五個(gè)屬性偏移量進(jìn)行初始化。

總結(jié)

AQS的三板斧就介紹完啦,我們?cè)賮砗唵位仡櫹拢?/p>

AQS(AbstractQueuedSynchronizer)是java.util .concurrent工具包下的抽象類,它通過實(shí)現(xiàn)MESA管程來解決并發(fā)領(lǐng)域中的同步與互斥問題。AQS實(shí)現(xiàn)中最重要的三點(diǎn)就是狀態(tài)、CAS和隊(duì)列,我們也稱之為AQS的三板斧。AQS的一切操作都是依據(jù)狀態(tài)state來的,它是被volatile修飾的全局變量,因此我們通過CAS操作使其線程安全。隊(duì)列是維護(hù)阻塞等待線程的容器,所有未獲得鎖或被要求等待的線程都會(huì)被包裝成Node放入隊(duì)列中。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 源碼
    +關(guān)注

    關(guān)注

    8

    文章

    643

    瀏覽量

    29251
  • 容器
    +關(guān)注

    關(guān)注

    0

    文章

    495

    瀏覽量

    22069
  • 模型
    +關(guān)注

    關(guān)注

    1

    文章

    3254

    瀏覽量

    48881
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    HMC253AQS24 Gerber Files

    HMC253AQS24 Gerber Files
    發(fā)表于 02-19 12:51 ?0次下載
    HMC253<b class='flag-5'>AQS</b>24 Gerber Files

    HMC241AQS16 S-Parameters

    HMC241AQS16 S-Parameters
    發(fā)表于 02-19 15:49 ?3次下載
    HMC241<b class='flag-5'>AQS</b>16 S-Parameters

    HMC245AQS16 Gerber Files

    HMC245AQS16 Gerber Files
    發(fā)表于 03-11 15:54 ?3次下載
    HMC245<b class='flag-5'>AQS</b>16 Gerber Files

    HMC241AQS16革資料

    HMC241AQS16 Gerber Files
    發(fā)表于 03-24 10:12 ?0次下載
    HMC241<b class='flag-5'>AQS</b>16革資料

    HMC253AQS24 S參數(shù)

    HMC253AQS24 S參數(shù)
    發(fā)表于 04-09 14:22 ?2次下載
    HMC253<b class='flag-5'>AQS</b>24 S參數(shù)

    HMC241AQS16 S參數(shù)

    HMC241AQS16 S參數(shù)
    發(fā)表于 04-09 14:24 ?1次下載
    HMC241<b class='flag-5'>AQS</b>16 S參數(shù)

    HMC253AQS24革資料

    HMC253AQS24革資料
    發(fā)表于 05-28 16:51 ?1次下載
    HMC253<b class='flag-5'>AQS</b>24革資料

    HMC253AQS24 S參數(shù)

    HMC253AQS24 S參數(shù)
    發(fā)表于 05-28 17:47 ?1次下載
    HMC253<b class='flag-5'>AQS</b>24 S參數(shù)

    HMC241AQS16 S參數(shù)

    HMC241AQS16 S參數(shù)
    發(fā)表于 05-30 11:18 ?0次下載
    HMC241<b class='flag-5'>AQS</b>16 S參數(shù)

    HMC245AQS16E S參數(shù)

    HMC245AQS16E S參數(shù)
    發(fā)表于 05-30 20:36 ?1次下載
    HMC245<b class='flag-5'>AQS</b>16E S參數(shù)

    HMC245AQS16革資料

    HMC245AQS16革資料
    發(fā)表于 06-01 12:30 ?0次下載
    HMC245<b class='flag-5'>AQS</b>16革資料

    HMC241AQS16革資料

    HMC241AQS16革資料
    發(fā)表于 06-01 15:13 ?0次下載
    HMC241<b class='flag-5'>AQS</b>16革資料

    AQS的同步組件有哪些呢?

    AQS 的全稱為 Abstract Queued Synchronizer,是在 JUC(java.util.concurrent)下子包中的類。
    的頭像 發(fā)表于 03-16 09:42 ?495次閱讀

    AQS如何解決線程同步與通信問題

    我們?cè)诘谝黄姓f到AQS使用的是管程模型,而管程模型是使用條件變量來解決同步通信問題的。條件變量會(huì)有兩個(gè)方法,喚醒和等待。當(dāng)條件滿足時(shí),我們會(huì)通過喚醒方法將條件隊(duì)列中的線程放入第二篇所說的同步隊(duì)列中
    的頭像 發(fā)表于 10-13 11:23 ?506次閱讀

    AQS獨(dú)占鎖的獲取

    AQS提供了兩種鎖,獨(dú)占鎖和共享鎖。獨(dú)占鎖只有一把鎖,同一時(shí)間只允許一個(gè)線程獲得鎖;而共享鎖則有多把鎖,同一時(shí)間允許多個(gè)線程獲得鎖。我們本文主要講獨(dú)占鎖。 一. 獨(dú)占鎖的獲取 AQS中對(duì)獨(dú)占鎖的獲取
    的頭像 發(fā)表于 10-13 14:51 ?476次閱讀
    <b class='flag-5'>AQS</b>獨(dú)占鎖的獲取