突擊并發(fā)編程JUC系列-原子更新AtomicLong

突擊并發(fā)編程JUC系列演示代碼地址:
https://github.com/mtcarpenter/JavaTutorial

JavaJDK 1.5開始提供了java.util.concurrent.atomic包(以下簡(jiǎn)稱Atomic包)绘盟,這個(gè)包中的原子操作類提供了一種用法簡(jiǎn)單、性能高效、線程安全地更新一個(gè)變量的方式。原子類通過 CAS (compare and swap)volatilenative方法實(shí)現(xiàn)勺拣,比 synchronized 開銷更小,執(zhí)行效率更高,在多線程環(huán)境下搞坝,無鎖的進(jìn)行原子操作。

Atomic包分類

對(duì)其進(jìn)行分類如下:

v2-0287f51914e342e9861f66f7734b311e_720w.png

為了避免一個(gè)章節(jié)內(nèi)容過多魁袜,導(dǎo)致大家提前下車桩撮,會(huì)通過幾個(gè)章節(jié)進(jìn)行 Atomic 包下面的知識(shí)講解敦第。

基本類型

基本類型有AtomicBooleanAtomicInteger 店量、AtomicLong芜果、這 3 個(gè)類提供的方法幾乎一模一樣,本章節(jié)以 AtomicLong為案例進(jìn)行講解融师,提前小劇透為了在后面和LongAdder 進(jìn)行對(duì)比右钾,LongAdderAtomicLong 在面試中也被問到過呢。

AtomicLong 的常用方法如下

方法名 說明
long getAndIncrement() 以原子方式將當(dāng)前值加1旱爆,注意舀射,返回的是舊值。(i++)
long incrementAndGet() 以原子方式將當(dāng)前值加1怀伦,注意脆烟,返回的是新值。(++i)
long getAndDecrement() 以原子方式將當(dāng)前值減 1空镜,注意浩淘,返回的是舊值 。(i--)
long decrementAndGet() 以原子方式將當(dāng)前值減 1吴攒,注意张抄,返回的是舊值 。(--i)
long addAndGet(int delta) 以原子方式將輸入的數(shù)值與實(shí)例中的值(AtomicLong里的value)相加洼怔,并返回結(jié)果
long getAndSet(int newValue) 以原子方式設(shè)置為newValue的值署惯,并返回舊值
long get() _獲取 AtomicLong 中的值(value)_
boolean compareAndSet(int expect,int update) 如果輸入的數(shù)值等于預(yù)期值镣隶,則以原子方式將該值設(shè)置為輸入的值极谊。
void lazySet(int newValue) 最終會(huì)設(shè)置成newValue,使用lazySet設(shè)置值后安岂,可能導(dǎo)致其他線程在之后的一小段時(shí)間內(nèi)還是可以讀到舊的值轻猖。
........ .........
JDK 1.8 新增
long getAndUpdate(LongUnaryOperator updateFunction) 定函數(shù)的結(jié)果原子更新當(dāng)前值,返回上一個(gè)值域那。
long updateAndGet(LongUnaryOperator updateFunction) 使用給定函數(shù)的結(jié)果原子更新當(dāng)前值咙边,返回更新的值。 該功能應(yīng)該是無副作用的次员,因?yàn)閲L試的更新由于線程之間的爭(zhēng)用而失敗時(shí)可能會(huì)被重新應(yīng)用败许。
........ ........

溫馨提示:i++++i淑蔚、i--市殷、--i只是為了幫助大家理解、理解刹衫、理解醋寝,重要的事情說三遍搞挣,并不是底層的實(shí)現(xiàn)就是它們喲。

小試牛刀

古人云“是騾子是馬拉出來溜溜“甥桂,一段代碼擼起來柿究,走你。

public class AtomicExample1 {
    /**
     * 初始化為 0
     */
    private static AtomicLong count = new AtomicLong(0);

    private static LongUnaryOperator longUnaryOperator = new LongUnaryOperator() {

        @Override
        public long applyAsLong(long operand) {
            return 1;
        }
    };

    private static LongBinaryOperator longBinaryOperator = new LongBinaryOperator() {
        @Override
        public long applyAsLong(long left, long right) {
            return left + right;
        }
    };

    public static void main(String[] args) {
        // 以原子方式將當(dāng)前值加1黄选,返回舊值 (i++): 0
        System.out.println("getAndIncrement=" + count.getAndIncrement());
        // 以原子方式將當(dāng)前值加1,返回新值(++i)  兩次增加 : 2
        System.out.println("incrementAndGet=" + count.incrementAndGet());
        //以原子方式將當(dāng)前值減少 1婶肩,返回舊值 (i--):2
        System.out.println("incrementAndGet=" + count.getAndDecrement());
        //以原子方式將當(dāng)前值減少 1办陷,返回舊值 (--i):0
        System.out.println("incrementAndGet=" + count.decrementAndGet());
        // 以原子方式將輸入的數(shù)值與實(shí)例中的值(AtomicLong里的value)相加,并返回結(jié)果
        System.out.println("addAndGet=" + count.addAndGet(10));
        // 以原子方式設(shè)置為`newValue`的值律歼,并返回舊值
        System.out.println("getAndSet=" + count.getAndSet(100));
        // 獲取 atomicLong 的 value
        System.out.println("get=" + count.get());

        System.out.println("*********** JDK 1.8 ***********");
        // 使用將給定函數(shù)定函數(shù)的結(jié)果原子更新當(dāng)前值民镜,返回上一個(gè)值
        // count.get() 為 1:返回 1
        System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));
        // 返回 applyAsLong 得值
        System.out.println("getAndUpdate=" + count.getAndUpdate(longUnaryOperator));

        // 獲取 atomicLong 的 value
        System.out.println("get=" + count.get());

        // 使用給定函數(shù)應(yīng)用給當(dāng)前值和給定值的結(jié)果原子更新當(dāng)前值,返回上一個(gè)值
        // 返回結(jié)果 1险毁,上次結(jié)果
        System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
        // 返回結(jié)果 3 制圈,上次結(jié)果 1 + 2
        System.out.println("getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
        // 獲取 atomicLong 的 value
        System.out.println("get=" + count.get());
    }
}

一串代碼送給你,運(yùn)行結(jié)果請(qǐng)參考:

getAndIncrement=0
incrementAndGet=2
incrementAndGet=2
incrementAndGet=0
addAndGet=10
getAndSet=10
get=100
*********** JDK 1.8 ***********
getAndUpdate=100
getAndUpdate=1
get=1
getAndAccumulate=1
getAndAccumulate=3
get=5

不安全并發(fā)計(jì)數(shù)

public class AtomicExample2 {

    // 請(qǐng)求總數(shù)
    public static int requestTotal = 1000;


    public static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        long start = System.currentTimeMillis();
        for (int i = 0; i < requestTotal; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                add();
                countDownLatch.countDown();
            }).start();

        }
        countDownLatch.await();
        System.out.println("count=" + count);
        System.out.println("耗時(shí):" + (System.currentTimeMillis() - start));

    }

    private static void add() {
        ++count;
    }
}

懵懂少年是否對(duì) CountDownLatch 有疑問嗎畔况?<br />CountDownLatch 又稱 倒計(jì)數(shù)器 , 也就是讓一個(gè)線程或者多個(gè)線程等待其他線程結(jié)束后再繼續(xù)自己的操作鲸鹦,類似加強(qiáng)版 join()

  • countDown : 執(zhí)行一次跷跪, 計(jì)數(shù)器的數(shù)值 -1馋嗜。
  • await :等待計(jì)算器的值為 0,才進(jìn)行后面的操作吵瞻,就像一個(gè)柵欄一樣葛菇。

AtomicLong 實(shí)現(xiàn)并發(fā)計(jì)數(shù)

public class AtomicExample3  {

    // 請(qǐng)求總數(shù)
    public static int requestTotal = 5000;

    public static AtomicLong count = new AtomicLong(0);


    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        long start = System.currentTimeMillis();
        for (int i = 0; i < requestTotal; i++) {
            new Thread(() -> {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                add();
                countDownLatch.countDown();
            }).start();

        }
        countDownLatch.await();
        System.out.println("count=" + count.get());
        System.out.println("耗時(shí):" + (System.currentTimeMillis() - start));
        count.addAndGet(200);
        System.out.println("count=" + count.get());

    }

    private static void add() {
        //count.incrementAndGet();
        count.getAndIncrement();
    }
}

走進(jìn)源碼

一段段小小的案例演示,已經(jīng)無法滿足懵懂少年了橡羞,那就加餐眯停,加餐,下面分類介紹下面 JDk 1.7JDK1.8 的底層實(shí)現(xiàn)卿泽。

在 Jdk1.7 中莺债,AtomicLong 的關(guān)鍵代碼如下:

        
    static {
      try {
        // 獲取內(nèi)存 value 內(nèi)存中的地址  
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
      } catch (Exception ex) { throw new Error(ex); }
    }

    // 省略其他代碼.....

    public final long getAndIncrement() {
       for(;;)
            long current = get();
            long next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

    public final boolean compareAndSet(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }

getAndIncrement()進(jìn)去乍一看,無限循環(huán)又厉,這不就如一個(gè)癡情男孩一樣九府,一直等待他的女神回信,不回信一直等啊等覆致。

  • long current = get(); 獲取 AtomicLong中的 value 值侄旬。<br />
  • long next = current + 1;: 在當(dāng)前記錄 + 1。
  • compareAndSet(current, next): 通過 compareAndSet方法來進(jìn)行原子更新操作煌妈,將當(dāng)前的值跟內(nèi)存中的值進(jìn)行比較儡羔,相等宣羊,則內(nèi)存中沒有被修改,直接寫入新的值到主內(nèi)存中汰蜘,并return true仇冯,否則直接return false。

在 Jdk1.8 中族操,AtomicLong 的關(guān)鍵代碼如下:

/**
     *  原子更新導(dǎo)致值
     *
     * @return 返回舊值
     */
    public final long getAndIncrement() {
        return unsafe.getAndAddLong(this, valueOffset, 1L);
    }
    // 
    public final long getAndAddLong(Object var1, long var2, long var4) {
        long var6;
        do {
            var6 = this.getLongVolatile(var1, var2);
        } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
        return var6;
    }
  • var1: 需要修改的類對(duì)象<br />
  • var2:修改的字段的內(nèi)存地址<br />
  • var6 是修改前字段的值苛坚,若是沒其余線程修改即與 var2 相等<br />
  • var6+var4: 修改后字段的值,也就是新值<br />
  • compareAndSwapLong :當(dāng)字段實(shí)際值和var6值相當(dāng)?shù)臅r(shí)候色难,才會(huì)設(shè)置其為 var6+var4 泼舱。
  • this.getLongVolatile(var1, var2):獲取對(duì)象obj中偏移量為offset的變量對(duì)應(yīng) volatile 語義的值。

從上面的代碼可以看出AtomicLong在 jdk 7 的循環(huán)邏輯枷莉,在 JDK 8 中原子操作類 unsafe 內(nèi)置了娇昙。之所以內(nèi)置應(yīng)該是考慮到這個(gè)函數(shù)在其他地方也會(huì)用到,而內(nèi)置可以提高復(fù)用性笤妙。<br />


歡迎關(guān)注公眾號(hào) 山間木匠 冒掌, 我是小春哥,從事 Java 后端開發(fā)蹲盘,會(huì)一點(diǎn)前端股毫、通過持續(xù)輸出系列技術(shù)文章與文會(huì)友,如果本文能為您提供幫助辜限,歡迎大家關(guān)注皇拣、 點(diǎn)贊、分享支持薄嫡,我們下期再見氧急!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市毫深,隨后出現(xiàn)的幾起案子吩坝,更是在濱河造成了極大的恐慌,老刑警劉巖哑蔫,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钉寝,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡闸迷,警方通過查閱死者的電腦和手機(jī)嵌纲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腥沽,“玉大人逮走,你說我怎么就攤上這事〗裱簦” “怎么了师溅?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵茅信,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我墓臭,道長(zhǎng)蘸鲸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任窿锉,我火速辦了婚禮酌摇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗡载。我一直安慰自己妙痹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布鼻疮。 她就那樣靜靜地躺著,像睡著了一般琳轿。 火紅的嫁衣襯著肌膚如雪判沟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天崭篡,我揣著相機(jī)與錄音挪哄,去河邊找鬼。 笑死琉闪,一個(gè)胖子當(dāng)著我的面吹牛迹炼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播颠毙,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼斯入,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蛀蜜?” 一聲冷哼從身側(cè)響起刻两,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎滴某,沒想到半個(gè)月后磅摹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霎奢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年户誓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幕侠。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帝美,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出橙依,到底是詐尸還是另有隱情证舟,我是刑警寧澤硕旗,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站女责,受9級(jí)特大地震影響漆枚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抵知,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一墙基、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刷喜,春花似錦残制、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至浊闪,卻和暖如春恼布,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搁宾。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工折汞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盖腿。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓爽待,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親翩腐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鸟款,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355