什么是AQS?

AQS實(shí)現(xiàn)原理

AQS中維護(hù)了一個(gè)volatile int state(共享資源)和一個(gè)CLH隊(duì)列。當(dāng)state=1時(shí)代表當(dāng)前對(duì)象鎖已經(jīng)被占用,其他線程來(lái)加鎖時(shí)則會(huì)失敗,失敗的線程被放入一個(gè)FIFO的等待隊(duì)列中,然后會(huì)被UNSAFE.park()操作掛起,等待已經(jīng)獲得鎖的線程釋放鎖才能被喚醒。

我們拿具體場(chǎng)景來(lái)分析,假設(shè)同時(shí)有三個(gè)線程并發(fā)搶占鎖,此時(shí)線程一搶占成功,線程二、三搶占失敗,具體流程如下:

此時(shí)AQS內(nèi)部數(shù)據(jù)結(jié)構(gòu)為:

上圖可以看到等待隊(duì)列中的節(jié)點(diǎn)Node是一個(gè)雙向鏈表,這里SIGNAL是Node中waitStatus屬性。

以非公平鎖看下具體實(shí)現(xiàn):

java.util.concurrent.locks.ReentrantLock.NonfairSync:

static final class NonfairSync extends Sync {
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

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

線程進(jìn)來(lái)直接利用CAS嘗試搶占鎖,如果搶占成功state值會(huì)被修改為1,且設(shè)置對(duì)象獨(dú)占鎖線程為當(dāng)前線程。

線程搶占實(shí)現(xiàn)#

線程二搶占失敗,執(zhí)行acquire(1)方法。

java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire()

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

tryAcquire是AbstractQueuedSynchronizer的方法,未提供對(duì)應(yīng)實(shí)現(xiàn),由子類實(shí)現(xiàn):

java.util.concurrent.locks.ReentrantLock .nonfairTryAcquire()

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

nonfairTryAcquire()方法中首先會(huì)獲取state的值,如果不為0則說(shuō)明當(dāng)前對(duì)象的鎖已經(jīng)被其他線程占有,接著判斷占有鎖的線程是否為當(dāng)前線程,如果是則累加state值,這里其實(shí)就是可重入鎖的具體實(shí)現(xiàn)。如果state為0,則執(zhí)行CAS操作,嘗試更新state值為1,如果更新成功則代表當(dāng)前線程加鎖成功。

當(dāng)前線程二執(zhí)行tryAcquire()后返回false,接著執(zhí)行addWaiter(Node.EXCLUSIVE)邏輯,將自己加入到一個(gè)FIFO等待隊(duì)列中,代碼實(shí)現(xiàn)如下:

java.util.concurrent.locks.AbstractQueuedSynchronizer.addWaiter()

private Node addWaiter(Node mode) {    
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

此時(shí)隊(duì)列中tail指針為空,直接調(diào)用enq(node)方法將當(dāng)前線程加入等待隊(duì)列尾部:

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

第一次循環(huán)時(shí)tail為空,創(chuàng)建一個(gè)哨兵節(jié)點(diǎn),head指向這個(gè)哨兵節(jié)點(diǎn);第二次循環(huán),將線程二對(duì)應(yīng)的node節(jié)點(diǎn)掛載到head節(jié)點(diǎn)后面并返回當(dāng)前線程創(chuàng)建的節(jié)點(diǎn)信息。繼續(xù)往后執(zhí)行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)邏輯,此時(shí)傳入的參數(shù)為線程二對(duì)應(yīng)的node節(jié)點(diǎn)信息。

java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued()

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndChecknIterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

acquireQueued()會(huì)先判斷當(dāng)前傳入的Node對(duì)應(yīng)的前置節(jié)點(diǎn)是否為head,如果是則嘗試加鎖。加鎖成功則將當(dāng)前節(jié)點(diǎn)設(shè)置為head節(jié)點(diǎn),然后刪除之前的head節(jié)點(diǎn)。

如果加鎖失敗或者Node的前置節(jié)點(diǎn)不是head節(jié)點(diǎn),就會(huì)通過(guò)shouldParkAfterFailedAcquire方法將head節(jié)點(diǎn)的waitStatus變成SIGNAL=-1,最后執(zhí)行parkAndChecknIterrupt方法,調(diào)用LockSupport.park()掛起當(dāng)前線程。此時(shí)線程二需要等待其他線程釋放鎖來(lái)喚醒。

線程釋放實(shí)現(xiàn)#

線程一執(zhí)行完后釋放鎖,具體代碼如下:

java.util.concurrent.locks.AbstractQueuedSynchronizer.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;
}

先執(zhí)行tryRelease方法,如果執(zhí)行成功,則繼續(xù)判斷head節(jié)點(diǎn)的waitStatus是否為0,這個(gè)值為SIGNAL=-1不為0,繼續(xù)執(zhí)行unparkSuccessor()方法喚醒head的后置節(jié)點(diǎn)。

ReentrantLock.tryRelease():

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

執(zhí)行完ReentrantLock.tryRelease()后,state被設(shè)置為0,Lock對(duì)象的獨(dú)占鎖被設(shè)置為null。

接著執(zhí)行java.util.concurrent.locks.AbstractQueuedSynchronizer.unparkSuccessor()方法,喚醒head的后置節(jié)點(diǎn):

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    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)
        LockSupport.unpark(s.thread);
}

這里主要是將head節(jié)點(diǎn)的waitStatus設(shè)置為0,然后解除head節(jié)點(diǎn)next的指向,使head幾點(diǎn)空置,等待被垃圾回收。

此時(shí)重新將head指針指向線程二對(duì)應(yīng)的Node節(jié)點(diǎn),且使用LockSupport.unpark方法來(lái)喚醒線程二。被喚醒的線程會(huì)接著嘗試獲取鎖,用CAS指令修改state數(shù)據(jù)。執(zhí)行完成后AQS中的數(shù)據(jù)結(jié)構(gòu)如下:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,263評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,946評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,409評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,641評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,872評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

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