Java中CAS 基本實(shí)現(xiàn)原理

一邻奠、前言

了解CAS,首先要清楚JUC,那么什么是JUC呢晦鞋?JUC就是java.util.concurrent包的簡(jiǎn)稱得糜。它有核心就是CAS與AQS载绿。CAS是java.util.concurrent.atomic包的基礎(chǔ)是钥,如AtomicInteger掠归、AtomicBoolean、AtomicLong等等類都是基于CAS悄泥。

什么是CAS呢虏冻?全稱Compare And Swap,比較并交換弹囚。CAS有三個(gè)操作數(shù)厨相,內(nèi)存值V,舊的預(yù)期值E,要修改的新值N蛮穿。當(dāng)且僅當(dāng)預(yù)期值E和內(nèi)存值V相同時(shí)庶骄,將內(nèi)存值V修改為N,否則什么都不做践磅。

二单刁、實(shí)例

如果我們需要對(duì)一個(gè)數(shù)進(jìn)行加法操作,應(yīng)該怎樣去實(shí)現(xiàn)呢府适?我們模擬多個(gè)線程情況下進(jìn)行操作羔飞。

ThreadDemo.java 實(shí)現(xiàn)一個(gè)Runnable接口

package com.spring.security.test;

public class ThreadDemo implements Runnable {

    private int count = 0;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            addCount();
        }
    }

    private void addCount() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

ThreadTest.java 創(chuàng)建線程池,提交10個(gè)線程執(zhí)行檐春,預(yù)期結(jié)果應(yīng)該是1000

package com.spring.security.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadTest {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        ThreadDemo threadDemo = new ThreadDemo();
        for (int i = 0; i < 10; i++) {
            threadPool.submit(threadDemo);
        }
        threadPool.shutdown();
        System.out.println(threadDemo.getCount());
    }
}

運(yùn)行結(jié)果:874 或其他逻淌,與預(yù)期結(jié)果不符合。

執(zhí)行出來(lái)的結(jié)果并不是想象中的結(jié)果疟暖。這是為什么呢卡儒?這跟線程的執(zhí)行過(guò)程有關(guān)。

所以我們需要在改變count俐巴,將值從高速緩沖區(qū)刷新到主內(nèi)存后骨望,讓其他線程重新讀取主內(nèi)存中的值到自己的工作內(nèi)存。

此時(shí)可以用volatile關(guān)鍵字欣舵。它的作用是保證對(duì)象在內(nèi)存中的可見(jiàn)性锦募。

修改ThreadDemo中的count字段

private volatile int count = 0;

此時(shí)執(zhí)行結(jié)果:900 或其他,與預(yù)期結(jié)果不符合邻遏。

此時(shí)還是并未得出正確執(zhí)行結(jié)果。為什么虐骑?聽(tīng)我細(xì)細(xì)道來(lái)准验。

線程安全主要體現(xiàn)在三個(gè)方面:

  • 原子性:提供了互斥訪問(wèn),同一時(shí)刻只能有一個(gè)線程對(duì)它進(jìn)行操作
  • 可見(jiàn)性:一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)的被其他線程觀察到
  • 有序性:一個(gè)線程觀察其他線程中的指令執(zhí)行順序廷没,由于指令重排序的存在糊饱,該觀察結(jié)果一般雜亂無(wú)序

目前可見(jiàn)性已經(jīng)實(shí)現(xiàn)了,缺少原子性的操作颠黎,因?yàn)橥粫r(shí)刻另锋,多個(gè)線程對(duì)其操作,會(huì)將改動(dòng)后的最新值讀取到自己的工作內(nèi)存進(jìn)行操作狭归,最終只能得到后一個(gè)執(zhí)行線程操作的結(jié)果夭坪,所以相當(dāng)于少了一步操作,就會(huì)造成數(shù)據(jù)的不一致过椎。

此時(shí)可以使用JUC的Atomic包下面的類來(lái)進(jìn)行操作室梅。

Atomic類是使用CAS+volatile來(lái)實(shí)現(xiàn)原子性與可見(jiàn)性的

我們來(lái)改造一下TheadDemo.java中的實(shí)現(xiàn)方法

package com.spring.security.test;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadDemo implements Runnable {

    private AtomicInteger count = new AtomicInteger(0);

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            // 遞增
            count.getAndIncrement();
        }
    }

    public int getCount() {
        return count.get();
    }
}

執(zhí)行結(jié)果: 1000,符合預(yù)期值亡鼠。

接下來(lái)我們來(lái)分析一下AtomicInteger類的源碼:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

Unsafe類是不安全的類赏殃,它提供了一些底層的方法,我們是不能使用這個(gè)類的间涵。AtomicInteger的值保存在value中仁热,而valueOffset是value在內(nèi)存中的偏移量,利用靜態(tài)代碼塊使其類一加載的時(shí)候就賦值勾哩。value值使用volatile抗蠢,保證其可見(jiàn)性。

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

var1表示當(dāng)前對(duì)象钳幅,var2表示value在內(nèi)存中的偏移量物蝙,var4為增加的值。var5為調(diào)用底層方法獲取value的值

compareAndSwapInt方法通過(guò)var1和var2獲取當(dāng)前內(nèi)存中的value值敢艰,并與var5進(jìn)行比對(duì)诬乞,如果一致,就將var5+var4的值賦給value,并返回true,否則返回false

由do while語(yǔ)句可知钠导,如果這次沒(méi)有設(shè)置進(jìn)去值震嫉,就重復(fù)執(zhí)行此過(guò)程。這一過(guò)程稱為自旋牡属。

compareAndSwapInt是JNI(Java Native Interface)提供的方法票堵,可以是其他語(yǔ)言寫(xiě)的。

三逮栅、與synchronized比較

使用synchronized進(jìn)行加法:

package com.spring.security.test;

public class ThreadDemo implements Runnable {

    private int count = 0;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            // 遞增
            synchronized (ThreadDemo.class) {
                count++;
            }
        }
    }

    public int getCount() {
        return count;
    }
}

運(yùn)行結(jié)果: 1000悴势,符合預(yù)期值。

使用synchronized和AtomicInteger都能得到預(yù)期結(jié)果措伐,但是他們之間各有什么劣勢(shì)呢特纤?

synchronized是重量級(jí)鎖,是悲觀鎖侥加,就是無(wú)論你線程之間發(fā)不發(fā)生競(jìng)爭(zhēng)關(guān)系捧存,它都認(rèn)為會(huì)發(fā)生競(jìng)爭(zhēng),從而每次執(zhí)行都會(huì)加鎖担败。

在并發(fā)量大的情況下昔穴,如果鎖的時(shí)間較長(zhǎng),那將會(huì)嚴(yán)重影響系統(tǒng)性能提前。

CAS操作中我們可以看到getAndAddInt方法的自旋操作吗货,如果長(zhǎng)時(shí)間自旋,那么肯定會(huì)對(duì)系統(tǒng)造成壓力岖研。而且如果value值從A->B->A,那么CAS就會(huì)認(rèn)為這個(gè)值沒(méi)有被操作過(guò)卿操,這個(gè)稱為CAS操作的"ABA"問(wèn)題警检。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市害淤,隨后出現(xiàn)的幾起案子扇雕,更是在濱河造成了極大的恐慌,老刑警劉巖窥摄,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镶奉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡崭放,警方通過(guò)查閱死者的電腦和手機(jī)哨苛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)币砂,“玉大人建峭,你說(shuō)我怎么就攤上這事【龃荩” “怎么了亿蒸?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)掌桩。 經(jīng)常有香客問(wèn)我边锁,道長(zhǎng),這世上最難降的妖魔是什么波岛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任茅坛,我火速辦了婚禮,結(jié)果婚禮上则拷,老公的妹妹穿的比我還像新娘贡蓖。我一直安慰自己,他們只是感情好煌茬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布摩梧。 她就那樣靜靜地躺著,像睡著了一般宣旱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叛薯,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天浑吟,我揣著相機(jī)與錄音,去河邊找鬼耗溜。 笑死组力,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抖拴。 我是一名探鬼主播燎字,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼腥椒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了候衍?” 一聲冷哼從身側(cè)響起笼蛛,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛉鹿,沒(méi)想到半個(gè)月后滨砍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妖异,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年惋戏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片响逢。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖棕孙,靈堂內(nèi)的尸體忽然破棺而出舔亭,到底是詐尸還是另有隱情,我是刑警寧澤散罕,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布分歇,位于F島的核電站,受9級(jí)特大地震影響欧漱,放射性物質(zhì)發(fā)生泄漏职抡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一误甚、第九天 我趴在偏房一處隱蔽的房頂上張望缚甩。 院中可真熱鬧,春花似錦窑邦、人聲如沸擅威。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)郊丛。三九已至,卻和暖如春瞧筛,著一層夾襖步出監(jiān)牢的瞬間厉熟,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工较幌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留揍瑟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓乍炉,卻偏偏與公主長(zhǎng)得像绢片,于是被迫代替她去往敵國(guó)和親滤馍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348