AQS CAS簡(jiǎn)單詳解

可參考:

[toc]

CAS(Compare And Swap)

什么是CAS

CAS(Compare And Swap),即比較并交換绸狐。是解決多線程并行情況下使用鎖造成性能損耗的一種機(jī)制正卧,CAS操作包含三個(gè)操作數(shù)——內(nèi)存位置(V)滚朵、預(yù)期原值(A)和新值(B)摘仅。如果內(nèi)存位置的值與預(yù)期原值相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值炕倘。否則斋陪,處理器不做任何操作。無論哪種情況辱挥,它都會(huì)在CAS指令之前返回該位置的值犁嗅。CAS有效地說明了“我認(rèn)為位置V應(yīng)該包含值A(chǔ);如果包含該值晤碘,則將B放到這個(gè)位置褂微;否則,不要更改該位置园爷,只告訴我這個(gè)位置現(xiàn)在的值即可宠蚂。

在JAVA中,sun.misc.Unsafe 類提供了硬件級(jí)別的原子操作來實(shí)現(xiàn)這個(gè)CAS童社。 java.util.concurrent 包下的大量類都使用了這個(gè) Unsafe.java 類的CAS操作求厕。至于 Unsafe.java 的具體實(shí)現(xiàn)這里就不討論了。詳細(xì)可以參考:java中的Unsafe

CAS典型應(yīng)用

java.util.concurrent.atomic 包下的類大多是使用CAS操作來實(shí)現(xiàn)的(eg. AtomicInteger.java,AtomicBoolean,AtomicLong)扰楼。下面以 AtomicInteger.java 的部分實(shí)現(xiàn)來大致講解下這些原子類的實(shí)現(xiàn)呀癣。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;
 
    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
 
    private volatile int value;// 初始int大小
    // 省略了部分代碼...
 
    // 帶參數(shù)構(gòu)造函數(shù),可設(shè)置初始int大小
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    // 不帶參數(shù)構(gòu)造函數(shù),初始int大小為0
    public AtomicInteger() {
    }
 
    // 獲取當(dāng)前值
    public final int get() {
        return value;
    }
 
    // 設(shè)置值為 newValue
    public final void set(int newValue) {
        value = newValue;
    }
 
    //返回舊值弦赖,并設(shè)置新值為 newValue
    public final int getAndSet(int newValue) {
        /**
        * 這里使用for循環(huán)不斷通過CAS操作來設(shè)置新值
        * CAS實(shí)現(xiàn)和加鎖實(shí)現(xiàn)的關(guān)系有點(diǎn)類似樂觀鎖和悲觀鎖的關(guān)系
        * */
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }
 
    // 原子的設(shè)置新值為update, expect為期望的當(dāng)前的值
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
 
    // 獲取當(dāng)前值current项栏,并設(shè)置新值為current+1
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }
 
    // 此處省略部分代碼,余下的代碼大致實(shí)現(xiàn)原理都是類似的
}

一般來說在競(jìng)爭(zhēng)不是特別激烈的時(shí)候蹬竖,使用該包下的原子操作性能比使用 synchronized 關(guān)鍵字的方式高效的多(查看getAndSet()沼沈,可知如果資源競(jìng)爭(zhēng)十分激烈的話,這個(gè)for循環(huán)可能換持續(xù)很久都不能成功跳出案腺。不過這種情況可能需要考慮降低資源競(jìng)爭(zhēng)才是)庆冕。
在較多的場(chǎng)景我們都可能會(huì)使用到這些原子類操作。一個(gè)典型應(yīng)用就是計(jì)數(shù)了劈榨,在多線程的情況下需要考慮線程安全問題访递。通常第一映像可能就是:

public class Counter {
    private int count;
    public Counter(){}
    public int getCount(){
        return count;
    }
    public void increase(){
        count++;
    }
}

上面這個(gè)類在多線程環(huán)境下會(huì)有線程安全問題,要解決這個(gè)問題最簡(jiǎn)單的方式可能就是通過加鎖的方式,調(diào)整如下:

public class Counter {
    private int count;
    public Counter(){}
    public synchronized int getCount(){
        return count;
    }
    public synchronized void increase(){
        count++;
    }
}

這類似于悲觀鎖的實(shí)現(xiàn)同辣,我需要獲取這個(gè)資源拷姿,那么我就給他加鎖,別的線程都無法訪問該資源旱函,直到我操作完后釋放對(duì)該資源的鎖响巢。我們知道,悲觀鎖的效率是不如樂觀鎖的棒妨,上面說了Atomic下的原子類的實(shí)現(xiàn)是類似樂觀鎖的踪古,效率會(huì)比使用 synchronized 關(guān)系字高,推薦使用這種方式,實(shí)現(xiàn)如下:

public class Counter {
    private AtomicInteger count = new AtomicInteger();
    public Counter(){}
    public int getCount(){
        return count.get();
    }
    public void increase(){
        count.getAndIncrement();
    }
}

AQS(AbstractQueuedSynchronizer)

什么是AQS

AQS(AbstractQueuedSynchronizer)伏穆,AQS是JDK下提供的一套用于實(shí)現(xiàn)基于FIFO等待隊(duì)列的阻塞鎖和相關(guān)的同步器的一個(gè)同步框架拘泞。這個(gè)抽象類被設(shè)計(jì)為作為一些可用原子int值來表示狀態(tài)的同步器的基類。如果你有看過類似 CountDownLatch 類的源碼實(shí)現(xiàn)枕扫,會(huì)發(fā)現(xiàn)其內(nèi)部有一個(gè)繼承了 AbstractQueuedSynchronizer 的內(nèi)部類 Sync陪腌。可見 CountDownLatch 是基于AQS框架來實(shí)現(xiàn)的一個(gè)同步器.類似的同步器在JUC下還有不少烟瞧。(eg. Semaphore)

AQS用法

如上所述诗鸭,AQS管理一個(gè)關(guān)于狀態(tài)信息的單一整數(shù),該整數(shù)可以表現(xiàn)任何狀態(tài)参滴。比如强岸, Semaphore 用它來表現(xiàn)剩余的許可數(shù),ReentrantLock 用它來表現(xiàn)擁有它的線程已經(jīng)請(qǐng)求了多少次鎖卵洗;FutureTask 用它來表現(xiàn)任務(wù)的狀態(tài)(尚未開始请唱、運(yùn)行、完成和取消)

To use this class as the basis of a synchronizer, redefine the
 * following methods, as applicable, by inspecting and/or modifying
 * the synchronization state using {@link #getState}, {@link
 * #setState} and/or {@link #compareAndSetState}:
 *
 * <ul>
 * <li> {@link #tryAcquire}
 * <li> {@link #tryRelease}
 * <li> {@link #tryAcquireShared}
 * <li> {@link #tryReleaseShared}
 * <li> {@link #isHeldExclusively}
 * </ul>

如JDK的文檔中所說过蹂,使用AQS來實(shí)現(xiàn)一個(gè)同步器需要覆蓋實(shí)現(xiàn)如下幾個(gè)方法十绑,并且使用getState,setState,compareAndSetState這幾個(gè)方法來設(shè)置獲取狀態(tài)

  1. boolean tryAcquire(int arg)
  2. boolean tryRelease(int arg)
  3. int tryAcquireShared(int arg)
  4. boolean tryReleaseShared(int arg)
  5. boolean isHeldExclusively()

以上方法不需要全部實(shí)現(xiàn),根據(jù)獲取的鎖的種類可以選擇實(shí)現(xiàn)不同的方法酷勺,支持獨(dú)占(排他)獲取鎖的同步器應(yīng)該實(shí)現(xiàn)tryAcquire本橙、 tryRelease、isHeldExclusively而支持共享獲取的同步器應(yīng)該實(shí)現(xiàn)tryAcquireShared脆诉、tryReleaseShared甚亭、isHeldExclusively。下面以 CountDownLatch 舉例說明基于AQS實(shí)現(xiàn)同步器, CountDownLatch 用同步狀態(tài)持有當(dāng)前計(jì)數(shù)击胜,countDown方法調(diào)用 release從而導(dǎo)致計(jì)數(shù)器遞減亏狰;當(dāng)計(jì)數(shù)器為0時(shí),解除所有線程的等待偶摔;await調(diào)用acquire暇唾,如果計(jì)數(shù)器為0,acquire 會(huì)立即返回辰斋,否則阻塞策州。通常用于某任務(wù)需要等待其他任務(wù)都完成后才能繼續(xù)執(zhí)行的情景。源碼如下:

public class CountDownLatch {
    /**
     * 基于AQS的內(nèi)部Sync
     * 使用AQS的state來表示計(jì)數(shù)count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;
 
        Sync(int count) {
            // 使用AQS的getState()方法設(shè)置狀態(tài)
            setState(count);
        }
 
        int getCount() {
            // 使用AQS的getState()方法獲取狀態(tài)
            return getState();
        }
 
        // 覆蓋在共享模式下嘗試獲取鎖
        protected int tryAcquireShared(int acquires) {
            // 這里用狀態(tài)state是否為0來表示是否成功宫仗,為0的時(shí)候可以獲取到返回1够挂,否則不可以返回-1
            return (getState() == 0) ? 1 : -1;
        }
 
        // 覆蓋在共享模式下嘗試釋放鎖
        protected boolean tryReleaseShared(int releases) {
            // 在for循環(huán)中Decrement count直至成功;
            // 當(dāng)狀態(tài)值即count為0的時(shí)候,返回false表示 signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
 
    private final Sync sync;
 
    // 使用給定計(jì)數(shù)值構(gòu)造CountDownLatch
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }
 
    // 讓當(dāng)前線程阻塞直到計(jì)數(shù)count變?yōu)?藕夫,或者線程被中斷
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
 
    // 阻塞當(dāng)前線程孽糖,除非count變?yōu)?或者等待了timeout的時(shí)間枯冈。當(dāng)count變?yōu)?時(shí),返回true
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
 
    // count遞減
    public void countDown() {
        sync.releaseShared(1);
    }
 
    // 獲取當(dāng)前count值
    public long getCount() {
        return sync.getCount();
    }
 
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末办悟,一起剝皮案震驚了整個(gè)濱河市霜幼,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌誉尖,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铸题,死亡現(xiàn)場(chǎng)離奇詭異铡恕,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)丢间,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門探熔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烘挫,你說我怎么就攤上這事诀艰。” “怎么了饮六?”我有些...
    開封第一講書人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵其垄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我卤橄,道長(zhǎng)绿满,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任窟扑,我火速辦了婚禮喇颁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嚎货。我一直安慰自己橘霎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開白布殖属。 她就那樣靜靜地躺著姐叁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忱辅。 梳的紋絲不亂的頭發(fā)上七蜘,一...
    開封第一講書人閱讀 51,521評(píng)論 1 304
  • 那天,我揣著相機(jī)與錄音墙懂,去河邊找鬼橡卤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛损搬,可吹牛的內(nèi)容都是我干的碧库。 我是一名探鬼主播柜与,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼嵌灰!你這毒婦竟也來了弄匕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤沽瞭,失蹤者是張志新(化名)和其女友劉穎迁匠,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驹溃,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡城丧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了豌鹤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亡哄。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖布疙,靈堂內(nèi)的尸體忽然破棺而出蚊惯,到底是詐尸還是另有隱情,我是刑警寧澤灵临,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布截型,位于F島的核電站,受9級(jí)特大地震影響儒溉,放射性物質(zhì)發(fā)生泄漏菠劝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一睁搭、第九天 我趴在偏房一處隱蔽的房頂上張望赶诊。 院中可真熱鬧,春花似錦园骆、人聲如沸舔痪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锄码。三九已至,卻和暖如春晌涕,著一層夾襖步出監(jiān)牢的瞬間滋捶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工余黎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留重窟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓惧财,卻偏偏與公主長(zhǎng)得像巡扇,于是被迫代替她去往敵國(guó)和親扭仁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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