[譯]Java并發(fā)之AtomicInteger

原文鏈接

AtomicInteger一個是專門被設(shè)計用來線程安全地更新Integer的類堂竟。為什么我們需要這個類呢郁竟?為什么不能僅僅就用一個volatile int ?我們能如何使用AtomicInteger?

為什么要用AtomicInteger?

下面展示了一個使用volatile int的非線程安全的counter例子:

public class CounterNotThreadSafe {
    private volatile int count = 0;
    public void increment() {
       count++;
    }
    public int getCount() {
        return count;
    }
}

你可以從GitHub下載示例源碼
我們把計數(shù)存在第二行的這個volatile int中州胳。我們需要volatile這個關(guān)鍵詞來確保所有線程總是能獲取到當(dāng)前的實(shí)際值,正如更多細(xì)節(jié)中描述的钦勘。我們在第四行使用++操作來給counter增加計數(shù)陋葡。為了確認(rèn)這個類是否是線程安全的,我們使用了如下的測試:

public class ConcurrencyTestCounter {
    private final CounterNotThreadSafe counter = new CounterNotThreadSafe();
    @Interleave
    private void increment() {
        counter.increment();
    }

    @Test
    public void testCounter() throws InterruptedException {
        Thread first = new Thread(() -> {increment();});
        Thread second = new Thread(() -> {increment();});
        first.start();
        second.start();
        first.join();
        second.join();
        assertEquals(2, counter.getCount());
}
}

我們需要兩個線程來測試counter是否是線程安全的彻采,分別在第9和第10行創(chuàng)建腐缤。
我們在11和12行啟動這兩個線程。然后肛响,在13和14行使用thread.join()等到兩個線程都結(jié)束岭粤。在兩個線程都結(jié)束后,在15行檢驗(yàn)是否counter的值是2特笋。

為了使所有的線程并發(fā)地測試剃浇,在第三行使用了來自vmlens的InterLeave注解。
Interleave注解會告知vmlens測試被注解的方法時所有的線程交替執(zhí)行猎物。運(yùn)行該測試虎囚,我們會看到這樣的錯誤:

ConcurrencyTestCounter.testCounter:22 expected:<2> but was:<1>

出現(xiàn)這個錯誤是由于++操作并非是原子性的,這兩個線程會互相覆蓋對方的運(yùn)算結(jié)果蔫磨。從vmlens的報告中我們可以知道:


測試報告

在這種錯誤下淘讥,兩個都線程首先都并行地讀取變量值,然后都對變量進(jìn)行了寫堤如,這就導(dǎo)致了錯誤值1蒲列。
要解決這個bug,可以使用AtomicInteger類:

public class CounterUsingIncrement {
    private final AtomicInteger count = new AtomicInteger();
    public void increment() {
        count.incrementAndGet();
    }
    public int getCount() {  
        return count.get();
    }
}

在第2行搀罢,我們將用AtomicInteger而非int來定義變量count蝗岖。在第4行使用incrementAndGet代替++操作。
現(xiàn)在榔至,由于方法inncrementAndGet()是原子性的抵赢,其他的線程總是在這個方法調(diào)用之前或之后獲取到值,線程們不會覆蓋其他線程的計算結(jié)果唧取。所以铅鲤,在多線程并行下,現(xiàn)在count的值一直都是2兵怯。

如何使用AtomicInteger

AtomicInteger有多種方法允許我們原子地更新AtomicInteger變量。例如腔剂,increment()方法原子地增加AtomicInteger變量媒区,decrementAndGet()原子地減少AtomicInteger變量。
但是compareAndSet()方法比較特別,這個方法允許我們原子地實(shí)現(xiàn)任意計算袜漩。compareAndSet()方法有兩個參數(shù)绪爸,一個是預(yù)期值,一個是更新值宙攻。這個方法原子地檢查當(dāng)前值是否等于預(yù)期值奠货,如果是的話,這個方法會將值替換成更新值并返回true座掘;如果不是的話递惋,保留當(dāng)前值不變并返回false。
使用這個方法的目的是讓compareAndSet()檢查是否當(dāng)前值是否在我們計算新值的時候被其他線程修改了溢陪。如果沒有的話我們可以安全地更新當(dāng)前值萍虽。否則,我們需要用當(dāng)前被修改過的值重新計算新的值形真。
下面的例子展示了如何使用compareAndSet()來實(shí)現(xiàn)我們的counter:

public void increment() {
    int current = count.get();
    int newValue = current + 1;
    while(!count.compareAndSet(current, newValue)) {
            current = count.get();
            newValue = current + 1;
    }
}

在第2行我們先讀取當(dāng)前的值杉编,然后在第3行計算新的值。然后咆霜,在第4行我們使用compareAndSet()檢查是否有另一個線程已經(jīng)修改了當(dāng)前值邓馒。如果當(dāng)前值沒有被修改過,compareAndSet()會更新當(dāng)前值并返回true蛾坯。否則返回false光酣。由于這個測試可能會失敗多次,我們需要使用一個while循環(huán)偿衰。如果這個值被其他線程改變了挂疆,我們需要獲取獲取被修改過的當(dāng)前值(在第5行),然后計算出新的值(第6行)并嘗試再次進(jìn)行更新下翎。

結(jié)論

AtomicInteger使我們可以以線程安全的方式更新integer變量缤言。使用incrementAndGet()或者decrementAndGet()這些方法來做簡單類型的計算。使用get()和compareAndSet用于所有其他類型的計算视事。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胆萧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俐东,更是在濱河造成了極大的恐慌跌穗,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虏辫,死亡現(xiàn)場離奇詭異蚌吸,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)砌庄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門羹唠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奕枢,“玉大人,你說我怎么就攤上這事佩微》毂颍” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵哺眯,是天一觀的道長谷浅。 經(jīng)常有香客問我,道長奶卓,這世上最難降的妖魔是什么一疯? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮寝杖,結(jié)果婚禮上违施,老公的妹妹穿的比我還像新娘。我一直安慰自己瑟幕,他們只是感情好磕蒲,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著只盹,像睡著了一般辣往。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上殖卑,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天站削,我揣著相機(jī)與錄音,去河邊找鬼孵稽。 笑死许起,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的菩鲜。 我是一名探鬼主播园细,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼接校!你這毒婦竟也來了猛频?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蛛勉,失蹤者是張志新(化名)和其女友劉穎鹿寻,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诽凌,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡毡熏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了侣诵。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痢法。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡恬试,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疯暑,到底是詐尸還是另有隱情,我是刑警寧澤哑舒,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布妇拯,位于F島的核電站,受9級特大地震影響洗鸵,放射性物質(zhì)發(fā)生泄漏越锈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一膘滨、第九天 我趴在偏房一處隱蔽的房頂上張望甘凭。 院中可真熱鬧,春花似錦火邓、人聲如沸丹弱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躲胳。三九已至,卻和暖如春纤勒,著一層夾襖步出監(jiān)牢的瞬間坯苹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工摇天, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粹湃,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓泉坐,卻偏偏與公主長得像为鳄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子坚冀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345