AQS獨(dú)占鎖的獲取和釋放分析

AQS(同步器)是用來(lái)構(gòu)建鎖和其他同步組件的基礎(chǔ)框架锋喜。它的實(shí)現(xiàn)主要是依賴一個(gè)int成員變量來(lái)標(biāo)識(shí)同步狀態(tài)和一個(gè)同步隊(duì)列。同步器本身沒有實(shí)現(xiàn)任何同步接口盼樟,僅僅是定義了幾個(gè)protected修飾同步狀態(tài)的獲取和釋放的方法來(lái)供同步組件使用狸捕。(狀態(tài)的更新使用getState,setState以及compareAndSetState這三個(gè)方法秉犹。)

比如說(shuō)鎖:在鎖的實(shí)現(xiàn)中聚合同步器殖氏,利用同步器實(shí)現(xiàn)鎖的語(yǔ)義晚树。鎖是面向使用者的,它定義了使用者和鎖的接口雅采,但是隱藏了具體的實(shí)現(xiàn)細(xì)節(jié)爵憎。而同步器是面向鎖的實(shí)現(xiàn)著,它簡(jiǎn)化了鎖的實(shí)現(xiàn)方式婚瓜,屏蔽了同步狀態(tài)的管理宝鼓,線程的排隊(duì)、等待和喚醒等操作巴刻。

AQS使用模板方法設(shè)計(jì)模式愚铡,它將一些方法開放給子類去進(jìn)行重寫,而同步器給同步組件提供的模板方法又會(huì)重新調(diào)用子類重寫的方法胡陪。例如:tryAcquire()

protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
}

ReentrantLock中NonfairSync(繼承AQS)會(huì)重寫該方法為:

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

而AQS中的模板方法acquire():

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

AQS提供的模板方法分為三類:

  • 獨(dú)占式獲取與釋放同步狀態(tài)茂附;
  • 共享式獲取與釋放同步狀態(tài);
  • 查詢同步隊(duì)列中等待線程情況督弓;

AQS本身內(nèi)部定義一個(gè)靜態(tài)類Node,通過(guò)雙向鏈?zhǔn)浇Y(jié)構(gòu)構(gòu)成了同步隊(duì)列。Node維護(hù)了幾個(gè)屬性:

volatile int waitStatus //節(jié)點(diǎn)狀態(tài)
volatile Node prev //當(dāng)前節(jié)點(diǎn)/線程的前驅(qū)節(jié)點(diǎn)
volatile Node next; //當(dāng)前節(jié)點(diǎn)/線程的后繼節(jié)點(diǎn)
volatile Thread thread;//加入同步隊(duì)列的線程引用
Node nextWaiter;//等待隊(duì)列中的下一個(gè)節(jié)點(diǎn)

節(jié)點(diǎn)的狀態(tài):

int CANCELLED =  1//節(jié)點(diǎn)從同步隊(duì)列中取消
int SIGNAL    = -1//后繼節(jié)點(diǎn)的線程處于等待狀態(tài)乒验,
//如果當(dāng)前節(jié)點(diǎn)釋放同步狀態(tài)會(huì)通知后繼節(jié)點(diǎn)愚隧,使得后繼節(jié)點(diǎn)的線程能夠運(yùn)行;
int CONDITION = -2//當(dāng)前節(jié)點(diǎn)進(jìn)入等待隊(duì)列中
int PROPAGATE = -3//表示下一次共享式同步狀態(tài)獲取將會(huì)無(wú)條件傳播下去
int INITIAL = 0;//初始狀態(tài)

AQS重要成員變量:

private transient volatile Node head;
private transient volatile Node tail;

AQS實(shí)際上是通過(guò)頭尾指針來(lái)管理控制同步隊(duì)列,同時(shí)實(shí)現(xiàn)對(duì)獲取鎖失敗的線程進(jìn)行入隊(duì)操作狂塘,釋放鎖是完成對(duì)同步隊(duì)列中等待的線程進(jìn)行通知等核心操作录煤。

獨(dú)占鎖獲取鎖的分析

先調(diào)用acquire()方法,看是否獲取同步狀態(tài)(也就是是否加鎖成功)荞胡,如果成功直接返回妈踊,如果失敗則在調(diào)用addWatier(),然后在調(diào)用acquireQueued()方法。

public final void acquire(int arg) {
        //先看同步狀態(tài)是否獲取成功泪漂,如果成功則方法結(jié)束返回
        //若失敗則先調(diào)用addWaiter()方法再調(diào)用acquireQueued()方法
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

addWaiter()源碼如下:

private Node addWaiter(Node mode) {
    
        // 1. 將當(dāng)前線程構(gòu)建成Node類型
        Node node = new Node(Thread.currentThread(), mode);
       
        // 2. 當(dāng)前尾節(jié)點(diǎn)是否為null廊营?
        Node pred = tail;
        if (pred != null) {
            
            // 2.2 將當(dāng)前節(jié)點(diǎn)尾插入的方式插入同步隊(duì)列中
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
    
        // 2.1. 當(dāng)前同步隊(duì)列尾節(jié)點(diǎn)為null,
        //說(shuō)明當(dāng)前線程是第一個(gè)加入同步隊(duì)列進(jìn)行等待的線程
        enq(node);
        return node;
}

private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //1. 構(gòu)造頭結(jié)點(diǎn)
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 2. 尾插入萝勤,CAS操作失敗自旋嘗試
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
}

addWaiter()露筒、enq()分析如下:

  • 當(dāng)前同步對(duì)列的尾結(jié)點(diǎn)為null,調(diào)用enq()方法插入當(dāng)前節(jié)點(diǎn)
  • 當(dāng)前對(duì)列的尾結(jié)點(diǎn)不為null敌卓,則采用compareAndSetTail()把當(dāng)前節(jié)點(diǎn)插入同步隊(duì)列的尾部慎式,如果則采用compareAndSetTail失敗則繼續(xù)執(zhí)行enq()方法,里面會(huì)繼續(xù)采用自旋模式繼續(xù)插入趟径,直至成功為止瘪吏。

當(dāng)前節(jié)點(diǎn)(線程)已經(jīng)插入同步隊(duì)列,acquireQueued()方法保證同步隊(duì)列的節(jié)點(diǎn)獲取獨(dú)占鎖(排隊(duì)獲取鎖的過(guò)程)蜗巧。

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 1. 獲得當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn)
                final Node p = node.predecessor();
                // 2. 當(dāng)前節(jié)點(diǎn)能否獲取獨(dú)占式鎖                  
                // 2.1 如果當(dāng)前節(jié)點(diǎn)的先驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)
                //并且成功獲取同步狀態(tài)掌眠,即可以獲得獨(dú)占式鎖
                if (p == head && tryAcquire(arg)) {
                    //隊(duì)列頭指針用指向當(dāng)前節(jié)點(diǎn)
                    setHead(node);
                    //釋放前驅(qū)節(jié)點(diǎn)
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 2.2 獲取鎖失敗,線程進(jìn)入等待狀態(tài)等待獲取獨(dú)占式鎖
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

分析入下:

  • 首先獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)惧蛹,如果先去節(jié)點(diǎn)是頭結(jié)點(diǎn)并且已經(jīng)成功獲取同步狀態(tài)(成功獲取鎖)扇救,當(dāng)前節(jié)點(diǎn)也能獲取鎖,反之進(jìn)入等待狀態(tài)香嗓。
  • 如果當(dāng)前節(jié)點(diǎn)前驅(qū)幾點(diǎn)已經(jīng)獲取鎖迅腔,然后通過(guò)setHead()將當(dāng)前節(jié)點(diǎn)設(shè)置為頭結(jié)點(diǎn),前驅(qū)節(jié)點(diǎn)出對(duì)靠娱,與同步隊(duì)列斷開沧烈,方便回收。
  • 如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)沒有獲取同步狀態(tài),shouldParkAfterFailedAcquire()方法像云,通過(guò)compareAndSetWaitStatus設(shè)置當(dāng)前節(jié)點(diǎn)為signall也就是等待狀態(tài)锌雀。
  • parkAndCheckInterrupt()調(diào)用此方法通過(guò)LockSupport.part()將當(dāng)前線程阻塞。
222.jpg
獨(dú)占鎖釋放分析

release方法

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

unparkSuccessor(h)

private void unparkSuccessor(Node node) {
   
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        //后繼節(jié)點(diǎn)不為null時(shí)喚醒該線程
        LockSupport.unpark(s.thread);
}

分析如下:

  • compareAndSetWaitStatus()通過(guò)cas操作更改同步對(duì)列的同步狀態(tài)迅诬。
  • 獲取當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)腋逆,如果后繼節(jié)點(diǎn)不為空,則調(diào)用LockSupport.unpark()喚醒后繼節(jié)點(diǎn)包裝的線程侈贷。

總結(jié):

  • 線程獲取鎖失敗惩歉,線程被封裝成Node進(jìn)行入隊(duì)操作,核心方法在于addWaiter()和enq(),同時(shí)enq()完成對(duì)同步隊(duì)列的頭結(jié)點(diǎn)初始化工作以及CAS操作失敗的重試;
  • 線程獲取鎖是一個(gè)自旋的過(guò)程撑蚌,當(dāng)且僅當(dāng) 當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)并且成功獲得同步狀態(tài)時(shí)上遥,節(jié)點(diǎn)出隊(duì)即該節(jié)點(diǎn)引用的線程獲得鎖,否則争涌,當(dāng)不滿足條件時(shí)就會(huì)調(diào)用LookSupport.park()方法使得線程阻塞粉楚;
  • 釋放鎖的時(shí)候會(huì)喚醒后繼節(jié)點(diǎn);
總體來(lái)講:

在獲取同步狀態(tài)時(shí)亮垫,AQS維護(hù)一個(gè)同步隊(duì)列模软,獲取同步狀態(tài)失敗的線程會(huì)加入到隊(duì)列中進(jìn)行自旋;移除隊(duì)列(或停止自旋)的條件是前驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)并且成功獲得了同步狀態(tài)包警。在釋放同步狀態(tài)時(shí)撵摆,同步器會(huì)調(diào)用unparkSuccessor()方法喚醒后繼節(jié)點(diǎn)。

原文鏈接:https://juejin.im/post/5aeb07ab6fb9a07ac36350c8

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末害晦,一起剝皮案震驚了整個(gè)濱河市特铝,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌壹瘟,老刑警劉巖鲫剿,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異稻轨,居然都是意外死亡灵莲,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門殴俱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)政冻,“玉大人,你說(shuō)我怎么就攤上這事线欲∶鞒。” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵李丰,是天一觀的道長(zhǎng)苦锨。 經(jīng)常有香客問(wèn)我,道長(zhǎng)趴泌,這世上最難降的妖魔是什么舟舒? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮嗜憔,結(jié)果婚禮上秃励,老公的妹妹穿的比我還像新娘。我一直安慰自己吉捶,他們只是感情好莺治,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布廓鞠。 她就那樣靜靜地躺著,像睡著了一般谣旁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滋早,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天榄审,我揣著相機(jī)與錄音,去河邊找鬼杆麸。 笑死搁进,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的昔头。 我是一名探鬼主播饼问,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼揭斧!你這毒婦竟也來(lái)了莱革?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤讹开,失蹤者是張志新(化名)和其女友劉穎盅视,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旦万,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡闹击,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了成艘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赏半。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖淆两,靈堂內(nèi)的尸體忽然破棺而出断箫,到底是詐尸還是另有隱情,我是刑警寧澤琼腔,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布瑰枫,位于F島的核電站,受9級(jí)特大地震影響丹莲,放射性物質(zhì)發(fā)生泄漏光坝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一甥材、第九天 我趴在偏房一處隱蔽的房頂上張望盯另。 院中可真熱鬧,春花似錦洲赵、人聲如沸鸳惯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)芝发。三九已至绪商,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辅鲸,已是汗流浹背格郁。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留独悴,地道東北人例书。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像刻炒,于是被迫代替她去往敵國(guó)和親决采。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355