Java隊(duì)列同步器(AQS)

在java.util.concurrent.locks包中有很多Lock的實(shí)現(xiàn)類年枕,常用的有ReentrantLock倍谜、ReadWriteLock(實(shí)現(xiàn)類ReentrantReadWriteLock),內(nèi)部實(shí)現(xiàn)都依賴AbstractQueuedSynchronizer類。

隊(duì)列同步器AQS是用來(lái)構(gòu)建鎖或其他同步組件的基礎(chǔ)框架,內(nèi)部使用一個(gè)int成員變量表示同步狀態(tài),通過(guò)內(nèi)置的FIFO隊(duì)列來(lái)完成資源獲取線程的排隊(duì)工作亚情,其中內(nèi)部狀態(tài)state,等待隊(duì)列的頭節(jié)點(diǎn)head和尾節(jié)點(diǎn)head哈雏,都是通過(guò)volatile修飾楞件,保證了多線程之間的可見(jiàn)。

public abstract class AbstractQueuedSynchronizer extends
    AbstractOwnableSynchronizer implements java.io.Serializable { 
    //等待隊(duì)列的頭節(jié)點(diǎn)
    private transient volatile Node head;
    //等待隊(duì)列的尾節(jié)點(diǎn)
    private transient volatile Node tail;
    //同步狀態(tài)
    private volatile int state;
    protected final int getState() { return state;}
    protected final void setState(int newState) { state = newState;}
    ...
}

AQS內(nèi)部有一個(gè)FIFO的同步隊(duì)列裳瘪,用于完成同步狀態(tài)的管理土浸。

static final class Node {
    static final Node SHARED = new Node();
    static final Node EXCLUSIVE = null;
    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
    ...
}
同步隊(duì)列結(jié)構(gòu)

頭結(jié)點(diǎn)代表當(dāng)前持有鎖的線程,每當(dāng)有線程競(jìng)爭(zhēng)失敗彭羹,就會(huì)插入到隊(duì)列的尾部黄伊,tail節(jié)點(diǎn)始終指向隊(duì)列中的最后一個(gè)元素。

每個(gè)節(jié)點(diǎn)中派殷, 除了存儲(chǔ)了當(dāng)前線程还最,前后節(jié)點(diǎn)的引用以外,還有一個(gè)waitStatus變量毡惜,用于描述節(jié)點(diǎn)當(dāng)前的狀態(tài)拓轻。多線程并發(fā)執(zhí)行時(shí),隊(duì)列中會(huì)有多個(gè)節(jié)點(diǎn)存在经伙,這個(gè)waitStatus其實(shí)代表對(duì)應(yīng)線程的狀態(tài):有的線程可能獲取鎖因?yàn)槟承┰蚍艞壐?jìng)爭(zhēng)扶叉;有的線程在等待滿足條件,滿足之后才能執(zhí)行等等橱乱。一共有4種狀態(tài):

  1. CANCELLED 取消狀態(tài)
  2. SIGNAL 等待觸發(fā)狀態(tài)
  3. CONDITION 等待條件狀態(tài)
  4. PROPAGATE 狀態(tài)需要向后傳播

1 獨(dú)占模式獲取鎖操作

在將Node節(jié)點(diǎn)插入到尾部之后,并不是立即掛起該節(jié)點(diǎn)中線程粱甫,因?yàn)樵诓迦胨倪^(guò)程中泳叠,前面的線程可能已經(jīng)執(zhí)行完成,所以它會(huì)先進(jìn)行自旋操作acquireQueued(node, arg)茶宵,嘗試讓該線程重新獲取鎖危纫!當(dāng)條件滿足獲取到了鎖則可以從自旋過(guò)程中退出,否則繼續(xù)。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            //如果它的前繼節(jié)點(diǎn)為頭結(jié)點(diǎn),嘗試獲取鎖,獲取成功則返回           
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

如果沒(méi)獲取到鎖种蝶,則判斷是否應(yīng)該掛起契耿,而這個(gè)判斷通過(guò)它的前驅(qū)節(jié)點(diǎn)的waitStatus來(lái)確定。

rivate 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;
}

如果前驅(qū)節(jié)點(diǎn)的waitStatus為:

  • SIGNAL螃征,則返回true表示應(yīng)該掛起當(dāng)前線程搪桂,掛起該線程,并等待被喚醒,被喚醒后進(jìn)行中斷檢測(cè),如果發(fā)現(xiàn)當(dāng)前線程被中斷盯滚,那么拋出InterruptedException并退出循環(huán)
  • 大于0踢械,將前驅(qū)節(jié)點(diǎn)踢出隊(duì)列,返回false
  • 小于0魄藕,也是返回false内列,不過(guò)先將前驅(qū)節(jié)點(diǎn)waitStatus設(shè)置為SIGNAL,使得下次判斷時(shí),將當(dāng)前節(jié)點(diǎn)掛起

獲取獨(dú)占鎖流程總結(jié)如下:
AQS的模板方法acquire通過(guò)調(diào)用子類自定義實(shí)現(xiàn)的tryAcquire獲取同步狀態(tài)失敗后->將線程構(gòu)造成Node節(jié)點(diǎn)(addWaiter)->將Node節(jié)點(diǎn)添加到同步隊(duì)列對(duì)尾(addWaiter)->節(jié)點(diǎn)以自旋的方法獲取同步狀態(tài)(acquirQueued)背率。在節(jié)點(diǎn)自旋獲取同步狀態(tài)時(shí)话瞧,只有其前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)的時(shí)候才會(huì)嘗試獲取同步狀態(tài),如果該節(jié)點(diǎn)的前驅(qū)不是頭節(jié)點(diǎn)或者該節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)但獲取同步狀態(tài)失敗寝姿,則判斷當(dāng)前線程是否需要阻塞交排,如果需要阻塞則在被喚醒過(guò)后才返回。

2 獨(dú)占模式釋放鎖操作

既然是釋放,那肯定是持有鎖的該線程執(zhí)行釋放操作会油,即head節(jié)點(diǎn)中的線程釋放鎖个粱。
AQS中的release釋放同步狀態(tài)和acquire獲取同步狀態(tài)一樣,都是模板方法翻翩,tryRelease釋放的具體操作都有子類去實(shí)現(xiàn)都许,父類AQS只提供一個(gè)算法模板。

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
/**如果node的后繼節(jié)點(diǎn)不為空且不是作廢狀態(tài),則喚醒這個(gè)后繼節(jié)點(diǎn),否則從末尾開(kāi)始尋找合適的節(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);
}

首先調(diào)用子類的tryRelease()方法釋放鎖嫂冻,然后喚醒后繼節(jié)點(diǎn)胶征,在喚醒的過(guò)程中,需要判斷后繼節(jié)點(diǎn)是否滿足情況桨仿,如果后繼節(jié)點(diǎn)不是作廢狀態(tài)睛低,則喚醒這個(gè)后繼節(jié)點(diǎn),否則從tail節(jié)點(diǎn)向前尋找合適的節(jié)點(diǎn)服傍,如果找到钱雷,則喚醒之。

參考資料:

  1. 深入淺出java同步器AQS
  2. 深入學(xué)習(xí)java同步器AQS
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吹零,一起剝皮案震驚了整個(gè)濱河市罩抗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灿椅,老刑警劉巖套蒂,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钞支,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡操刀,警方通過(guò)查閱死者的電腦和手機(jī)烁挟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)骨坑,“玉大人撼嗓,你說(shuō)我怎么就攤上這事】▎” “怎么了静稻?”我有些...
    開(kāi)封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)匈辱。 經(jīng)常有香客問(wèn)我振湾,道長(zhǎng),這世上最難降的妖魔是什么亡脸? 我笑而不...
    開(kāi)封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任押搪,我火速辦了婚禮,結(jié)果婚禮上浅碾,老公的妹妹穿的比我還像新娘大州。我一直安慰自己,他們只是感情好垂谢,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布厦画。 她就那樣靜靜地躺著,像睡著了一般滥朱。 火紅的嫁衣襯著肌膚如雪根暑。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天徙邻,我揣著相機(jī)與錄音排嫌,去河邊找鬼。 笑死缰犁,一個(gè)胖子當(dāng)著我的面吹牛淳地,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帅容,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼颇象,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了并徘?” 一聲冷哼從身側(cè)響起遣钳,我...
    開(kāi)封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饮亏,沒(méi)想到半個(gè)月后耍贾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡路幸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年荐开,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片简肴。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡晃听,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出砰识,到底是詐尸還是另有隱情能扒,我是刑警寧澤,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布辫狼,位于F島的核電站初斑,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏膨处。R本人自食惡果不足惜见秤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望真椿。 院中可真熱鬧鹃答,春花似錦、人聲如沸突硝。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)解恰。三九已至锋八,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間修噪,已是汗流浹背查库。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留黄琼,地道東北人樊销。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像脏款,于是被迫代替她去往敵國(guó)和親围苫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

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