AQS(AbstractQueuedSynchronizer)源碼解析

前言

java.util.concurrent包(之后簡稱JUC包)中帅刊,提供了大量的同步與并發(fā)的工具類纪蜒,是多線程編程的“利器”。其中l(wèi)ocks包下,包含了多種鎖钳踊,如ReentrantLock獨(dú)占鎖非驮、ReentrantReadWriteLock讀寫鎖畦徘、Semaphore信號(hào)量(共享鎖)等纤掸,而這些鎖有一個(gè)共同的基礎(chǔ)類:AbstractQueuedSynchronizer。

原文地址:http://www.wangjialong.cc/2018/04/06/aqs_info/#more

AQS簡介

AQS是一個(gè)抽象類凫佛,不可以被實(shí)例化讲坎,它的設(shè)計(jì)之初就是為了讓子類通過繼承來實(shí)現(xiàn)多樣的功能的。它內(nèi)部提供了一個(gè)FIFO的等待隊(duì)列愧薛,用于多個(gè)線程等待一個(gè)事件(鎖)晨炕。它有一個(gè)重要的狀態(tài)標(biāo)志——state,該屬性是一個(gè)int值毫炉,表示對(duì)象的當(dāng)前狀態(tài)(如0表示lock瓮栗,1表示unlock)。AQS提供了三個(gè)protected final的方法來改變state的值瞄勾,分別是:getState费奸、setState(int)、compareAndSetState(int, int)进陡。根據(jù)修飾符货邓,它們是不可以被子類重寫的,但可以在子類中進(jìn)行調(diào)用四濒,這也就意味著子類可以根據(jù)自己的邏輯來決定如何使用state值。

/ 同一類中 同一包中 同包內(nèi)的子類 不同包內(nèi)的子類 全局
public + + + + +
protected + + + +
default(無修飾符) + + +
private +

java的修飾符作用域如下:

/ 同一類中 同一包中 同包內(nèi)的子類 不同包內(nèi)的子類 全局
public + + + + +
protected + + + +
default(無修飾符) + + +
private +

表中 + 表示可以訪問职辨, 空白表示無法訪問

AQS的子類應(yīng)當(dāng)被定義為內(nèi)部類盗蟆,作為內(nèi)部的helper對(duì)象。事實(shí)上舒裤,這也是juc種鎖的做法喳资,如ReentrantLock,便是通過內(nèi)部的Sync對(duì)象來繼承AQS的腾供。AQS中定義了一些未實(shí)現(xiàn)的方法(拋出UnsupportedOperationException異常)

  • tryAcquire(int) 嘗試獲取state
  • tryRelease(int) 嘗試釋放state
  • tryAcquireShared(int) 共享的方式嘗試獲取
  • tryReleaseShared(int) 共享的方式嘗試釋放
  • isHeldExclusively() 判斷當(dāng)前是否為獨(dú)占鎖

這些方法是子類需要實(shí)現(xiàn)的仆邓,可以選擇實(shí)現(xiàn)其中的一部分鲜滩。根據(jù)實(shí)現(xiàn)方式的不同,可以分為兩種:獨(dú)占鎖和共享鎖节值。其中JUC中鎖的分類為:

  • 獨(dú)占鎖:ReentrantLock徙硅、ReentrantReadWriteLock.WriteLock
  • 共享鎖:ReentrantReadWriteLock.ReadLock、CountDownLatch搞疗、CyclicBarrier嗓蘑、Semaphore

其實(shí)現(xiàn)方式為:

  • 獨(dú)占鎖實(shí)現(xiàn)的是tryAcquire(int)、tryRelease(int)
  • 共享鎖實(shí)現(xiàn)的是tryAcquireShared(int)匿乃、tryReleaseShared(int)

如獨(dú)占鎖的實(shí)現(xiàn)方式是:

Acquire:
     while (!tryAcquire(arg)) {
        //將當(dāng)前線程加入FIFO隊(duì)列中;
        //自旋或阻塞當(dāng)前線程;
     }

Release:
     if (tryRelease(arg))
        //喚醒隊(duì)列中的第一個(gè)線程(head);

AQS中還提供了一個(gè)內(nèi)部類ConditionObject桩皿,它實(shí)現(xiàn)了Condition接口,可以用于await/signal幢炸。采用CLH隊(duì)列的算法泄隔,喚醒當(dāng)前線程的下一個(gè)節(jié)點(diǎn)對(duì)應(yīng)的線程,而signalAll喚醒所有線程宛徊。

總的來說佛嬉,AQS提供了三個(gè)功能:

  1. 實(shí)現(xiàn)獨(dú)占鎖
  2. 實(shí)現(xiàn)共享鎖
  3. 實(shí)現(xiàn)Condition模型

源碼解析

Node解析

AQS內(nèi)部定義了一個(gè)static final的內(nèi)部類Node,用于實(shí)現(xiàn)等待隊(duì)列CLH岩调,滿足FIFO結(jié)構(gòu)巷燥,其隊(duì)列結(jié)構(gòu)如下所示:

<pre>
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
| | ----> | | ----> | |
+------+ next +-----+ +-----+
</pre>

隊(duì)列為一個(gè)雙向鏈表結(jié)構(gòu),保存了head号枕、tail兩個(gè)指針缰揪,分別指向鏈表頭部、尾部葱淳。當(dāng)需要添加節(jié)點(diǎn)時(shí)钝腺,直接在tail位置添加,而dequeue操作直接對(duì)head節(jié)點(diǎn)進(jìn)行赞厕。Node中定義如下常量:

/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();

/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;

/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED =  1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL    = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;

/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;

以上常量分別用于設(shè)置如下屬性的值:

volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;

Node類型的常量SHARED艳狐、EXCLUSIVE用于設(shè)置nextWaiter,用于表示當(dāng)前節(jié)點(diǎn)是共享的皿桑,還是互斥的毫目,分別用于共享鎖和獨(dú)占鎖。int類型的常量CANCELLED诲侮、SIGNAL镀虐、CONDITION、PROPAGATE用于設(shè)置waitStatus沟绪,用于在ConditionObject中使用刮便,可以實(shí)現(xiàn)await/signal模型。

Node有三個(gè)構(gòu)造函數(shù):

//不存放任何線程绽慈,用于生成哨兵節(jié)點(diǎn)
Node() ;
//用于鎖
Node(Thread thread, Node mode) ;
//用于Condition
Node(Thread thread, int waitStatus) ;

AQS屬性

AQS使用內(nèi)部類Node恨旱,構(gòu)造一個(gè)雙向鏈表辈毯,用作FIFO隊(duì)列;除此之外搜贤,AQS還存放一個(gè)int類型的屬性state谆沃,用于表示當(dāng)前的同步狀態(tài)。

//鏈表頭節(jié)點(diǎn)
private transient volatile Node head;
//鏈表尾節(jié)點(diǎn)
private transient volatile Node tail;
//同步狀態(tài)
private volatile int state;

head節(jié)點(diǎn)是一個(gè)哨兵節(jié)點(diǎn)入客,不存放實(shí)際的“線程”節(jié)點(diǎn)(使用Node的無參構(gòu)造函數(shù))管毙。tail指向鏈表的最后一個(gè)節(jié)點(diǎn),當(dāng)新增節(jié)點(diǎn)時(shí)桌硫,將新節(jié)點(diǎn)作為當(dāng)前tail的下一個(gè)節(jié)點(diǎn)夭咬,通過CAS設(shè)置成功后,將新節(jié)點(diǎn)設(shè)為新的tail節(jié)點(diǎn)即可。新增節(jié)點(diǎn)的源碼如下:

    private Node enq(final Node node) {
        for (;;) { //死循環(huán)
            Node t = tail;
            if (t == null) { // 空鏈表铆隘,head卓舵、tail都為空
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

enq操作是一個(gè)無限循環(huán)的操作,最終總會(huì)成功膀钠,但根據(jù)代碼可知掏湾,AQS應(yīng)不是starvation free的,因?yàn)槟硞€(gè)線程可能會(huì)持續(xù)的enq失敗肿嘲。AQS提供了形如doAcquireNanos方法融击,但其超時(shí)返回false操作是在addWaiter方法(內(nèi)部調(diào)用enq)之后,也無法回避enq的starvation雳窟。在此順便說一下尊浪,AQS也是無法保證fair的,也就是說先入隊(duì)列的線程不一定先獲取到鎖封救。節(jié)點(diǎn)的CAS是通過Unsafe來實(shí)現(xiàn)的拇涤,在state中統(tǒng)一說明。

state表示AQS當(dāng)前的同步狀態(tài)誉结,如0表示lock鹅士,1表示unlock狀態(tài)。對(duì)state的操作惩坑,提供了三個(gè)方法掉盅。

    //讀取當(dāng)前state
    protected final int getState() {
        return state;
    }

    //直接寫入,不考慮當(dāng)前值
    protected final void setState(int newState) {
        state = newState;
    }

    //保證讀-寫的原子性
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

可以看到compareAndSetState使用的是unsafe對(duì)象的compareAndSwapInt方法以舒,傳入this指針怔接,state屬性的偏移地址,期待值expect稀轨,更新值update,可以實(shí)現(xiàn)CAS操作岸军。state屬性的偏移地址獲取方式如下:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long stateOffset;
static {
    try {
        stateOffset = unsafe.objectFieldOffset
                        (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
    } catch (Exception ex) { throw new Error(ex); }
}

實(shí)際上奋刽,AQS的head瓦侮、tail節(jié)點(diǎn),內(nèi)部類Node的waitStatus佣谐、next屬性均使用unsafe對(duì)象肚吏,通過偏移地址來進(jìn)行CAS操作。Unsafe是sun.misc包下的類狭魂,在Java API中沒有官方文檔罚攀,因?yàn)樗怯糜趯?shí)現(xiàn)Java庫的,Java中有一個(gè)功能類似的類雌澄,可以實(shí)現(xiàn)對(duì)象屬性的CAS操作斋泄,可以參考我的另一篇博客AtomicXFieldUpdater,屬性原子修改的外部工具類镐牺,關(guān)于Unsafe的使用炫掐,可以參考Guide to Unsafe

AQS還有一個(gè)屬性static final long spinForTimeoutThreshold = 1000L;睬涧,用于表示自旋的時(shí)間募胃,小于1000納秒的采用自旋鎖,大于1000納秒畦浓,使用LockSupport.park方法痹束,將線程掛起。

重要方法分析

AQS是用于實(shí)現(xiàn)獨(dú)占鎖或共享鎖的讶请,對(duì)于一個(gè)鎖來說祷嘶,最重要的就是lock和unlock操作了,對(duì)應(yīng)到AQS中秽梅,為acquire抹蚀、release方法,由于AQS需要和子類進(jìn)行“合作”企垦,因此需要子類的定義來進(jìn)行聯(lián)合分析环壤,為簡單起見,使用AQS官方文檔中的示例钞诡,定義獨(dú)占鎖如下:

class Mutex implements Lock, java.io.Serializable {

   // Our internal helper class
   private static class Sync extends AbstractQueuedSynchronizer {
     // Reports whether in locked state
     protected boolean isHeldExclusively() {
       return getState() == 1;
     }

     // Acquires the lock if state is zero
     public boolean tryAcquire(int acquires) {
       assert acquires == 1; // Otherwise unused
       if (compareAndSetState(0, 1)) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
     }

     // Releases the lock by setting state to zero
     protected boolean tryRelease(int releases) {
       assert releases == 1; // Otherwise unused
       if (getState() == 0) throw new IllegalMonitorStateException();
       setExclusiveOwnerThread(null);
       setState(0);
       return true;
     }

     // Provides a Condition
     Condition newCondition() { return new ConditionObject(); }

     // Deserializes properly
     private void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException {
       s.defaultReadObject();
       setState(0); // reset to unlocked state
     }
   }

   // The sync object does all the hard work. We just forward to it.
   private final Sync sync = new Sync();

   public void lock()                { sync.acquire(1); }
   public boolean tryLock()          { return sync.tryAcquire(1); }
   public void unlock()              { sync.release(1); }
   public Condition newCondition()   { return sync.newCondition(); }
   public boolean isLocked()         { return sync.isHeldExclusively(); }
   public boolean hasQueuedThreads() { return sync.hasQueuedThreads(); }
   public void lockInterruptibly() throws InterruptedException {
     sync.acquireInterruptibly(1);
   }
   public boolean tryLock(long timeout, TimeUnit unit)
       throws InterruptedException {
     return sync.tryAcquireNanos(1, unit.toNanos(timeout));
   }
 }

可以看到郑现,lock方法調(diào)用內(nèi)部類的acquire方法,也就是AQS的acquire方法荧降。unlock方法調(diào)用release方法接箫。
下面對(duì)兩個(gè)流程進(jìn)行分析

acquire

acquire是獨(dú)占鎖的獲取鎖的方法,其源碼如下:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

acquire方法非常簡單朵诫,如果tryAcquire失斝劣选(返回false),則調(diào)用acquireQueued方法,將當(dāng)前線程加入到等待隊(duì)列中废累,并中斷當(dāng)前線程邓梅,等待喚醒。

tryAcquire由子類實(shí)現(xiàn)邑滨,下面先分析acquireQueued方法日缨。

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //若當(dāng)前節(jié)點(diǎn)為鏈表第一個(gè)節(jié)點(diǎn)
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //park當(dāng)前線程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

acquireQueued在addWaiter之后,再次嘗試獲取鎖掖看,與tryAcquire不同的是匣距,返回true表示未獲取成功,false表示獲取成功哎壳。通過判斷當(dāng)前節(jié)點(diǎn)是否為隊(duì)列第一個(gè)節(jié)點(diǎn)毅待,來決定是否獲取成功,acquireQueued方法相當(dāng)于提供了一個(gè)默認(rèn)方法耳峦,會(huì)被子類的tryAcquire方法屏蔽掉(若tryAcquire返回true的話)恩静。

tryAcquire會(huì)調(diào)用子類Mutex.Sync的實(shí)現(xiàn),其代碼如下:

    // 如果state為0蹲坷,則獲取到鎖
    public boolean tryAcquire(int acquires) {
       assert acquires == 1; // Otherwise unused
       if (compareAndSetState(0, 1)) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
    }

由此可見,AQS提供了一個(gè)模板驶乾,子類需要實(shí)現(xiàn)其tryAcquire方法,實(shí)現(xiàn)具體的獲取鎖邏輯(通過對(duì)state的讀循签、寫)级乐,子類lock方法直接調(diào)用AQS的acquire方法即可。

release方法

Mutex的unlock方法調(diào)用了release方法县匠,在AQS中定義风科,源碼如下:

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

還是同樣的配方,release方法調(diào)用子類實(shí)現(xiàn)的tryRelease乞旦,返回true后贼穆,表示獲取成功,之后判斷頭節(jié)點(diǎn)兰粉,由于鎖的實(shí)現(xiàn)中故痊,waitStatus必定為0,所以不會(huì)執(zhí)行unpark操作玖姑,unpark用于Condition模型中愕秫。tryRelease方法的源碼如下:

    // 將state設(shè)置為0,解鎖
    protected boolean tryRelease(int releases) {
       assert releases == 1; // Otherwise unused
       if (getState() == 0) throw new IllegalMonitorStateException();
       setExclusiveOwnerThread(null);
       setState(0);
       return true;
     }

由源碼可知焰络,tryRelease只需要將state設(shè)置為0即可戴甩,因?yàn)檎{(diào)用unlock方法的必定是之前調(diào)用lock成功的,因此當(dāng)前state必定為1闪彼,為安全起見甜孤,使用getState判斷是否為0,若為0,說明線程出錯(cuò)缴川。state設(shè)置時(shí)囱稽,不需要調(diào)用CAS方法,只需要setState即可二跋,保證write對(duì)于其他線程可見即可(通過volatile內(nèi)存屏障保證)。

總結(jié)

AQS提供了一個(gè)框架流昏,在其上可以構(gòu)建豐富的線程同步工具類扎即,JUC包中ReadWriteLock、CountDownLatch都是基于AQS實(shí)現(xiàn)的况凉,AQS在JUC包中的地位相當(dāng)重要谚鄙。其類圖如下:

image

盜圖使用,詳見“JUC鎖”01之 框架

AQS提供了三大功能:獨(dú)占鎖、共享鎖刁绒、ConditionObject闷营。子類在實(shí)現(xiàn)中,可以實(shí)現(xiàn)其一部分方法知市。其編程思想值得借鑒傻盟,通過超類實(shí)現(xiàn)基本的處理流程,將其中部分抽成未實(shí)現(xiàn)方法嫂丙,默認(rèn)拋出異常娘赴,由子類實(shí)現(xiàn),這種解耦方式跟啤,最大化的減少了代碼的重復(fù)诽表,且便于子類在實(shí)現(xiàn)中個(gè)性化自己的處理邏輯。

很久沒寫博客了隅肥,準(zhǔn)備以AQS入手竿奏,深入分析一下JUC包,flag就這么立起來了腥放,希望可以實(shí)現(xiàn)吧~~

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泛啸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子捉片,更是在濱河造成了極大的恐慌平痰,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伍纫,死亡現(xiàn)場(chǎng)離奇詭異宗雇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)莹规,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門赔蒲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事舞虱』都剩” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵矾兜,是天一觀的道長损趋。 經(jīng)常有香客問我,道長椅寺,這世上最難降的妖魔是什么浑槽? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮返帕,結(jié)果婚禮上桐玻,老公的妹妹穿的比我還像新娘。我一直安慰自己荆萤,他們只是感情好镊靴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著链韭,像睡著了一般偏竟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梧油,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天苫耸,我揣著相機(jī)與錄音,去河邊找鬼儡陨。 笑死褪子,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的骗村。 我是一名探鬼主播嫌褪,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼胚股!你這毒婦竟也來了笼痛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤琅拌,失蹤者是張志新(化名)和其女友劉穎缨伊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體进宝,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刻坊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了党晋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谭胚。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡徐块,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灾而,到底是詐尸還是另有隱情胡控,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布旁趟,位于F島的核電站昼激,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏锡搜。R本人自食惡果不足惜癣猾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望余爆。 院中可真熱鬧,春花似錦夸盟、人聲如沸蛾方。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽桩砰。三九已至,卻和暖如春释簿,著一層夾襖步出監(jiān)牢的瞬間亚隅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國打工庶溶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煮纵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓偏螺,卻偏偏與公主長得像行疏,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子套像,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容