CAS和AQS

[TOC]

CAS

全稱(Compare And Swap),比較交換

Unsafe類是CAS的核心類,提供硬件級(jí)別的原子操作殴俱。

// 對(duì)象政冻、對(duì)象的地址、預(yù)期值线欲、修改值
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

缺點(diǎn):

  1. 開銷大:在并發(fā)量比較高的情況下明场,如果反復(fù)嘗試更新某個(gè)變量,卻又一直更新不成功李丰,會(huì)給CPU帶來(lái)較大的壓力
  2. ABA問題:當(dāng)變量從A修改為B在修改回A時(shí)苦锨,變量值等于期望值A(chǔ),但是無(wú)法判斷是否修改,CAS操作在ABA修改后依然成功舟舒。
    • 如何避免:Java提供了AtomicStampedReference和AtomicMarkableReference來(lái)解決拉庶。AtomicStampedReference通過(guò)包裝[E,Integer]的元組來(lái)對(duì)對(duì)象標(biāo)記版本戳stamp,對(duì)于ABA問題其解決方案是加上版本號(hào)魏蔗,即在每個(gè)變量都加上一個(gè)版本號(hào)砍的,每次改變時(shí)加1,即A —> B —> A莺治,變成1A —> 2B —> 3A廓鞠。
  3. 不能保證代碼塊的原子性:CAS機(jī)制所保證的只是一個(gè)變量的原子性操作,而不能保證整個(gè)代碼塊的原子性谣旁。
public class Test {
    private static AtomicInteger atomicInteger = new AtomicInteger(100);
    private static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);

    public static void main(String[] args) throws InterruptedException {

        //AtomicInteger
        Thread at1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInteger.compareAndSet(100,110);
                atomicInteger.compareAndSet(110,100);
            }
        });

        Thread at2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);      // at1,執(zhí)行完
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicInteger:" + atomicInteger.compareAndSet(100,120));
            }
        });

        at1.start();
        at2.start();

        at1.join();
        at2.join();

        //AtomicStampedReference

        Thread tsf1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //讓 tsf2先獲取stamp床佳,導(dǎo)致預(yù)期時(shí)間戳不一致
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 預(yù)期引用:100,更新后的引用:110榄审,預(yù)期標(biāo)識(shí)getStamp() 更新后的標(biāo)識(shí)getStamp() + 1
                atomicStampedReference.compareAndSet(100,110,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
                atomicStampedReference.compareAndSet(110,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp() + 1);
            }
        });

        Thread tsf2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedReference.getStamp();

                try {
                    TimeUnit.SECONDS.sleep(2);      //線程tsf1執(zhí)行完
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("AtomicStampedReference:" +atomicStampedReference.compareAndSet(100,120,stamp,stamp + 1));
            }
        });

        tsf1.start();
        tsf2.start();
    }

}

AQS(AbstractQueuedSynchronizer)

維護(hù)一個(gè)volatile int state(代表共享資源狀態(tài))和一個(gè)FIFO線程等待隊(duì)列砌们。

模板方法基本分為三類:

  • 獨(dú)占鎖
  • 共享鎖
  • 釋放鎖

資源共享的方式

  1. Exclusive(獨(dú)占,只有一個(gè)線程能執(zhí)行搁进,如ReentrantLock)
  2. Share(共享浪感,多個(gè)線程可以同時(shí)執(zhí)行,如Semaphore/CountDownLatch)

同步隊(duì)列

AQS依靠同步隊(duì)列(一個(gè)FIFO的雙向隊(duì)列)來(lái)完成同步狀態(tài)的管理饼问。當(dāng)當(dāng)前線程獲取狀態(tài)失敗后影兽,同步器會(huì)將當(dāng)前線程以及等待信息構(gòu)造成一個(gè)節(jié)點(diǎn)(Node),并嘗試將他加入到同步隊(duì)列莱革。Head節(jié)點(diǎn)不保存等待的線程信息峻堰,僅通過(guò)next指向隊(duì)列中第一個(gè)保存等待線程信息的Node。

雙向同步隊(duì)列

Node類

源碼(中字注釋)

static final class Node {
    /** 代表共享模式 */
    static final Node SHARED = new Node();
    /** 代表獨(dú)占模式 */
    static final Node EXCLUSIVE = null;

    /** 以下四個(gè)狀態(tài)解釋見下文等待狀態(tài) */
    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;

    /** 標(biāo)識(shí)等待狀態(tài)盅视,通過(guò)CAS操作更新捐名,原子操作不會(huì)被打斷*/
    volatile int waitStatus;

    /** 當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn) */
    volatile Node prev;

    /** 當(dāng)前節(jié)點(diǎn)的后置節(jié)點(diǎn) */
    volatile Node next;

    /** 該節(jié)點(diǎn)關(guān)聯(lián)的線程(未能獲取鎖,進(jìn)入等待的線程) */
    volatile Thread thread;

    /** 指向下一個(gè)在某個(gè)條件上等待的節(jié)點(diǎn)闹击,或者指向 SHARE 節(jié)點(diǎn)镶蹋,表明當(dāng)前處于共享模式*/
    Node nextWaiter;

    /**
     * 判斷是否處于共享模式
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /** 
     * 返回當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn) 
     * 會(huì)做對(duì)前置節(jié)點(diǎn)空值判斷 
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}
等待狀態(tài):

等待狀態(tài)的修改是CAS原子操作

  • CANCELED: 1,因?yàn)榈却瑫r(shí) (timeout)或者中斷(interrupt)赏半,節(jié)點(diǎn)會(huì)被置為取消狀態(tài)贺归。處于取消狀態(tài)的節(jié)點(diǎn)不會(huì)再去競(jìng)爭(zhēng)鎖,也就是說(shuō)不會(huì)再被阻塞除破。節(jié)點(diǎn)會(huì)一直保持取消狀態(tài),而不會(huì)轉(zhuǎn)換為其他狀態(tài)琼腔。處于 CANCELED 的節(jié)點(diǎn)會(huì)被移出隊(duì)列瑰枫,被 GC 回收。
  • SIGNAL: -1,表明當(dāng)前的后繼結(jié)點(diǎn)正在或者將要被阻塞(通過(guò)使用 LockSupport.pack 方法)光坝,因此當(dāng)前的節(jié)點(diǎn)被釋放(release)或者被取消時(shí)(cancel)時(shí)尸诽,要喚醒它的后繼結(jié)點(diǎn)(通過(guò) LockSupport.unpark 方法)。
  • CONDITION: -2盯另,表明當(dāng)前節(jié)點(diǎn)在條件隊(duì)列中性含,因?yàn)榈却硞€(gè)條件而被阻塞。
  • PROPAGATE: -3鸳惯,在共享模式下商蕴,可以認(rèn)為資源有多個(gè),因此當(dāng)前線程被喚醒之后芝发,可能還有剩余的資源可以喚醒其他線程绪商。該狀態(tài)用來(lái)表明后續(xù)節(jié)點(diǎn)會(huì)傳播喚醒的操作。需要注意的是只有頭節(jié)點(diǎn)才可以設(shè)置為該狀態(tài)(This is set (for head node only) in doReleaseShared to ensure propagation continues, even if other operations have since intervened.)辅鲸。
  • 0:新創(chuàng)建的節(jié)點(diǎn)會(huì)處于這種狀態(tài)

鎖的獲取與釋放:

獲取獨(dú)占鎖
獲取獨(dú)占鎖
  • acquire方法
public final void acquire(int arg) {
    // 首先嘗試獲取鎖格郁,如果獲取失敗,會(huì)先調(diào)用 addWaiter 方法創(chuàng)建節(jié)點(diǎn)并追加到隊(duì)列尾部
    // 然后調(diào)用 acquireQueued 阻塞或者循環(huán)嘗試獲取鎖
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
        // 在 acquireQueued 中独悴,如果線程是因?yàn)橹袛喽顺龅淖枞麪顟B(tài)會(huì)返回 true
        // 這里的 selfInterrupt 主要是為了恢復(fù)線程的中斷狀態(tài)
        selfInterrupt();
    }
}
釋放獨(dú)占鎖
釋放獨(dú)占鎖

? 在獨(dú)占模式中例书,鎖的釋放由于沒有其他線程競(jìng)爭(zhēng),相對(duì)簡(jiǎn)單刻炒。鎖釋放失敗的原因是由于該線程本身不擁有鎖决采,而非多線程競(jìng)爭(zhēng)。鎖釋放成功后會(huì)檢查后置節(jié)點(diǎn)的狀態(tài)落蝙,找到合適的節(jié)點(diǎn)织狐,調(diào)用unparkSuccessor方法喚醒該節(jié)點(diǎn)所關(guān)聯(lián)的線程。

  • release方法
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        // waitStatus 為 0筏勒,證明是初始化的空隊(duì)列或者后繼結(jié)點(diǎn)已經(jīng)被喚醒了
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
獲取共享鎖
獲取共享鎖
  • acquireShared方法

    通過(guò)該方法可以申請(qǐng)鎖

public final void acquireShared(int arg) {
    // 如果返回結(jié)果小于0移迫,證明沒有獲取到共享資源
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
  • doAcquireShared
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
釋放共享鎖

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市管行,隨后出現(xiàn)的幾起案子厨埋,更是在濱河造成了極大的恐慌,老刑警劉巖捐顷,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件荡陷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡迅涮,警方通過(guò)查閱死者的電腦和手機(jī)废赞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)叮姑,“玉大人唉地,你說(shuō)我怎么就攤上這事据悔。” “怎么了耘沼?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵极颓,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我群嗤,道長(zhǎng)菠隆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任狂秘,我火速辦了婚禮骇径,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赃绊。我一直安慰自己既峡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布碧查。 她就那樣靜靜地躺著运敢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忠售。 梳的紋絲不亂的頭發(fā)上传惠,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音稻扬,去河邊找鬼卦方。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泰佳,可吹牛的內(nèi)容都是我干的盼砍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼逝她,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼浇坐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起黔宛,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤近刘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后臀晃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體觉渴,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年徽惋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了案淋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡险绘,死狀恐怖踢京,靈堂內(nèi)的尸體忽然破棺而出回右,到底是詐尸還是另有隱情,我是刑警寧澤漱挚,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站渺氧,受9級(jí)特大地震影響旨涝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜侣背,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一白华、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贩耐,春花似錦弧腥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至铡买,卻和暖如春更鲁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背奇钞。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工澡为, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人景埃。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓媒至,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親谷徙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拒啰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348