AQS簡單介紹

AQS簡單介紹

AbstractQueuedSynchronizer結(jié)構(gòu)簡單介紹

diagram-12266480935983140402.png

通過其內(nèi)部結(jié)構(gòu)大致可以了解到

1、AQS其實(shí)是一個雙向鏈表,有head湿痢、tail節(jié)點(diǎn)分別代表首尾節(jié)點(diǎn);每個節(jié)點(diǎn)的元素的類型是Node;

2霍骄、在AQS中維護(hù)了一個int類型的state屬性,這個屬性在不同的子類中存在不同的含義;例如在Semaphore中表示的就是信號量;在ReentrantLock中表示的是當(dāng)前線程獲取鎖的可重入次數(shù);

3台囱、在Node節(jié)點(diǎn)中的thread表示的是進(jìn)入到AQS中的線程;其中SHARED表示的是該線程是在獲取共享資源被阻塞后臺放入到阻塞隊列中的;EXCLUSIVE表示是在獲取獨(dú)占資源被阻塞后放入到阻塞隊列中的;在Node中的waitStatus表示的是對應(yīng)線程的狀態(tài),其中CANCELLED(1:線程已經(jīng)被取消)、SIGNAL(-1:表示線程需要被喚醒)读整、CONDITION(-2:線程在條件隊列里面等待)簿训、PROPAGATE(-3:釋放共享資源時需要通知其他線程)表示對應(yīng)的

大致了解之后,來看一下AQS中幾個相當(dāng)重要的方法;

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

1、該方法獲取獨(dú)占資源,先調(diào)用tryAcquire方法嘗試獲取獨(dú)占資源;該方法的實(shí)現(xiàn)有子類定義;

2绘沉、在獨(dú)占的資源沒有獲取到的時候,向隊列中添加一個EXCLUSIVE類型的的Node節(jié)點(diǎn)到AQS阻塞隊列的尾部;

3煎楣、然后調(diào)用LockSupport.park(this)掛起;

接下來一個簡單了解一下每個方法的內(nèi)部執(zhí)行邏輯

  • boolean addWaiter(Node mode)
    private Node addWaiter(Node mode) {
        //(1)
        Node node = new Node(Thread.currentThread(), mode);
        //(2)
        Node pred = tail;
        //(3)
        if (pred != null) {
            //(3.1)
            node.prev = pred;
            //(3.2)
            if (compareAndSetTail(pred, node)) {
                //(3.2.1)
                pred.next = node;
                //(3.2.2)
                return node;
            }
        }
        //(4)
        enq(node);
        return node;
    }

(1)為當(dāng)前線程構(gòu)建一個新的node節(jié)點(diǎn);

(2)獲取AQS隊列中的尾節(jié)點(diǎn);

(3)判斷尾節(jié)點(diǎn)是否不為null;

(3.1)將新的節(jié)點(diǎn)的前置節(jié)點(diǎn)設(shè)置為尾節(jié)點(diǎn);

(3.2)通過CAS將當(dāng)前的尾節(jié)點(diǎn)設(shè)置為新的節(jié)點(diǎn);這里使用CAS的原因是為了防止其他的線程將AQS隊列中的tail元素修改成了別的Node元素;

(3.2.1)CAS操作成功之后將將尾節(jié)點(diǎn)的后置節(jié)點(diǎn)設(shè)置為新的節(jié)點(diǎn);操作失敗進(jìn)入(4)

(3.2.2)返回新節(jié)點(diǎn)

(4)當(dāng)前AQS隊列中的tail節(jié)點(diǎn)為null,那么這個時候可以認(rèn)為AQS隊列為null,然后通過enq(fianl Node node)方法進(jìn)行元素新增;

接下來簡單分析一下enq(fianl Node node)方法;

  • Node enq(final Node node)
    private Node enq(final Node node) {
        for (;;) {
            //(1)
            Node t = tail;
            //(2)
            if (t == null) { // Must initialize
                //(2.1)
                if (compareAndSetHead(new Node()))
                    //(2.2)
                    tail = head;
            //(3)
            } else {
                //(3.1)
                node.prev = t;
                //(3.2)
                if (compareAndSetTail(t, node)) {
                    //(3.3)
                    t.next = node;
                    return t;
                }
            }
        }
    }

此方法根據(jù)boolean addWaiter(Node mode)方法的執(zhí)行情況進(jìn)行分析;

1豺总、當(dāng)AQS中的tail節(jié)點(diǎn)為null時,進(jìn)入此方法,也就是第一次循環(huán)

(1)這里重新做一次取值,是為了避免在此過程中,tail被其他線程修改了;這里默認(rèn)沒有被修改,那么在(2)中t== null成立;

(2.1)中將head節(jié)點(diǎn)設(shè)置為哨兵節(jié)點(diǎn)(new Node());
(2.2)head節(jié)點(diǎn)設(shè)置成功之后,將head節(jié)點(diǎn)賦值tail節(jié)點(diǎn);這個熟tail節(jié)點(diǎn)就不為空了;

2车伞、(2.2)中將head節(jié)點(diǎn)的值賦值給了tail節(jié)點(diǎn)之后,第一次循環(huán)結(jié)束,進(jìn)行第二次循環(huán),這次循環(huán)很明顯tail != null了

然后進(jìn)入(3)

(3.1)將新增的節(jié)點(diǎn)的前置節(jié)點(diǎn)只想哨兵節(jié)點(diǎn)

(3.2)設(shè)置尾節(jié)點(diǎn)tail尾新增的節(jié)點(diǎn)node;

(3.3)將哨兵節(jié)點(diǎn)的后置節(jié)點(diǎn)指向新增的節(jié)點(diǎn)node,然后返回

執(zhí)行相關(guān)的大致流程圖:


AQS新增第一次循環(huán).png

AQS新增第二次循環(huán).png

然后再看acquireQueued(final Node node, int arg)方法

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

接著addWaiter方法來分析一下acquireQueued方法;

1、 獲取當(dāng)前節(jié)點(diǎn)的prev節(jié)點(diǎn)

2喻喳、 如果prev節(jié)點(diǎn)為head節(jié)點(diǎn)另玖,那么它就有資格去爭搶鎖,調(diào)用tryAcquire搶占鎖

3表伦、 搶占鎖成功以后谦去,把獲得鎖的節(jié)點(diǎn)設(shè)置為head,并且移除原來的初始化head節(jié)點(diǎn)

4蹦哼、如果獲得鎖失敗鳄哭,則根據(jù)waitStatus決定是否需要掛起線程

5、 最后纲熏,通過cancelAcquire取消獲得鎖的操作

2妆丘、boolean release(int arg)

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

1锄俄、該方法會先調(diào)用boolean tryRelease(int arg)方法嘗試釋放資源(具體實(shí)現(xiàn)由子類定義)

2、在釋放資源成功后,判斷AQS隊列中是否存在被阻塞的線程

3勺拣、存在被阻塞的線程則調(diào)用void unparkSuccessor(Node node)進(jìn)行喚醒;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載奶赠,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。
  • 序言:七十年代末药有,一起剝皮案震驚了整個濱河市毅戈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌愤惰,老刑警劉巖苇经,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異宦言,居然都是意外死亡塑陵,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蜡励,熙熙樓的掌柜王于貴愁眉苦臉地迎上來令花,“玉大人,你說我怎么就攤上這事凉倚〖娑迹” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵稽寒,是天一觀的道長扮碧。 經(jīng)常有香客問我,道長杏糙,這世上最難降的妖魔是什么慎王? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮宏侍,結(jié)果婚禮上赖淤,老公的妹妹穿的比我還像新娘。我一直安慰自己谅河,他們只是感情好咱旱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著绷耍,像睡著了一般吐限。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上褂始,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天诸典,我揣著相機(jī)與錄音,去河邊找鬼崎苗。 笑死狐粱,一個胖子當(dāng)著我的面吹牛赘阀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播脑奠,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼基公,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了宋欺?” 一聲冷哼從身側(cè)響起轰豆,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎齿诞,沒想到半個月后酸休,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祷杈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年斑司,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片但汞。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡宿刮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出私蕾,到底是詐尸還是另有隱情僵缺,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布踩叭,位于F島的核電站磕潮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏容贝。R本人自食惡果不足惜自脯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望斤富。 院中可真熱鬧膏潮,春花似錦、人聲如沸茂缚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脚囊。三九已至,卻和暖如春桐磁,著一層夾襖步出監(jiān)牢的瞬間悔耘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工我擂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衬以,地道東北人缓艳。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像看峻,于是被迫代替她去往敵國和親阶淘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

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