AtomicLong和LongAdder的區(qū)別

AtomicLong和LongAdder的區(qū)別

前言

最近在看到不少框架里面使用到了LongAdder這個(gè)類淡诗,而并非AtomicLong,很是困惑,于是專門(mén)看了LongAdder的源碼,總結(jié)一下這兩個(gè)的區(qū)別。

AtomicLong原理

就像我們所知道的那樣,AtomicLong的原理是依靠底層的cas來(lái)保障原子性的更新數(shù)據(jù)肢簿,在要添加或者減少的時(shí)候靶剑,會(huì)使用死循環(huán)不斷地cas到特定的值,從而達(dá)到更新數(shù)據(jù)的目的池充。那么LongAdder又是使用到了什么原理?難道有比cas更加快速的方式桩引?

LongAdder原理

首先我們來(lái)看一下LongAdder有哪些方法?

可以看到和AtomicLong基本類似,同樣有增加收夸、減少等操作坑匠,那么如何實(shí)現(xiàn)原子的增加呢?


這里寫(xiě)圖片描述

我們可以看到一個(gè)Cell的類,那這個(gè)類是用來(lái)干什么的呢?

我們可以看到Cell類的內(nèi)部是一個(gè)volatile的變量卧惜,然后更改這個(gè)變量唯一的方式通過(guò)cas厘灼。我們可以猜測(cè)到LongAdder的高明之處可能在于將之前單個(gè)節(jié)點(diǎn)的并發(fā)分散到各個(gè)節(jié)點(diǎn)的,這樣從而提高在高并發(fā)時(shí)候的效率咽瓷。

下面我們來(lái)驗(yàn)證我們的觀點(diǎn)设凹,我們接著看上圖的add方法,如果cell數(shù)組不為空茅姜,那么就嘗試更新base元素闪朱,如果更新成功,那么就直接返回钻洒。base元素在這里起到了一個(gè)什么作用呢?可以保障的是在低并發(fā)的時(shí)候和AtomicLong一樣的直接對(duì)基礎(chǔ)元素進(jìn)行更新奋姿。
??而如果cell為空或者更新base失敗,我們看接下來(lái)的那個(gè)if判斷素标,即如果as不為空并且成功更新對(duì)應(yīng)節(jié)點(diǎn)的數(shù)據(jù)称诗,則返回,否則就會(huì)進(jìn)入longAccumulate()方法头遭。
??圖有點(diǎn)大粪狼,無(wú)法截圖,直接貼源碼

        for (;;) {
            Cell[] as; Cell a; int n; long v;
            if ((as = cells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) { //如果對(duì)應(yīng)位置沒(méi)有數(shù)據(jù)任岸,那么直接插入元素
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)     //標(biāo)示沖突標(biāo)志位 再榄,進(jìn)行重試 CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale,如果已經(jīng)cell數(shù)組的大小已經(jīng)超過(guò)了CPU核數(shù)享潜,那么再擴(kuò)容沒(méi)意義了困鸥,直接重試,或者有別的線程擴(kuò)容導(dǎo)致變更了數(shù)組,設(shè)置標(biāo)示位疾就,進(jìn)行重試澜术,,避免一失敗就擴(kuò)容
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 && casCellsBusy()) {//開(kāi)始擴(kuò)容
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h); //擴(kuò)容完成以后猬腰,重新初始化要更新的索引值鸟废,盡量保障可以更新成功
            }
            else if (cellsBusy == 0 && cells == as && //初始化casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }

上面的代碼主要有三個(gè)分支:
????1. 如果數(shù)組不為空
????2. 數(shù)據(jù)為空,則初始化
????3. 前面都更新失敗了姑荷,嘗試更新base數(shù)據(jù)
?? cellBusy是一個(gè)標(biāo)示元素盒延,只有當(dāng)修改cell數(shù)組大小或者插入元素的時(shí)候才會(huì)修改。分支二鼠冕、分支三都很簡(jiǎn)單添寺,我們來(lái)重點(diǎn)分析一下分支一。
?? 當(dāng)要更新的位置沒(méi)有元素的時(shí)候懈费,首先cas標(biāo)志位计露,防止擴(kuò)容以及插入元素,然后插入數(shù)據(jù)憎乙。如果成功直接返回票罐,否則標(biāo)示發(fā)生了沖突,然后重試泞边。如果對(duì)應(yīng)的位置有元素則更新胶坠,如果更新失敗,進(jìn)行判斷是否數(shù)組的大小已經(jīng)超過(guò)了cpu的核數(shù)繁堡,如果大于的話沈善,則意味著擴(kuò)容沒(méi)有意義。直接重試椭蹄。否則進(jìn)行擴(kuò)容闻牡,擴(kuò)容完成后,重新設(shè)置要更新的位置绳矩,盡可能保證下一次更新成功罩润。
??我們來(lái)看一下如何統(tǒng)計(jì)計(jì)數(shù)。


當(dāng)計(jì)數(shù)的時(shí)候翼馆,將base和各個(gè)cell元素里面的值進(jìn)行疊加割以,從而得到計(jì)算總數(shù)的目的。這里的問(wèn)題是在計(jì)數(shù)的同時(shí)如果修改cell元素应媚,有可能導(dǎo)致計(jì)數(shù)的結(jié)果不準(zhǔn)確严沥。

總結(jié):

LongAdder在AtomicLong的基礎(chǔ)上將單點(diǎn)的更新壓力分散到各個(gè)節(jié)點(diǎn),在低并發(fā)的時(shí)候通過(guò)對(duì)base的直接更新可以很好的保障和AtomicLong的性能基本保持一致中姜,而在高并發(fā)的時(shí)候通過(guò)分散提高了性能消玄。
缺點(diǎn)是LongAdder在統(tǒng)計(jì)的時(shí)候如果有并發(fā)更新跟伏,可能導(dǎo)致統(tǒng)計(jì)的數(shù)據(jù)有誤差。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末翩瓜,一起剝皮案震驚了整個(gè)濱河市受扳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兔跌,老刑警劉巖勘高,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異坟桅,居然都是意外死亡华望,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)桦卒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人匿又,你說(shuō)我怎么就攤上這事方灾。” “怎么了碌更?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵裕偿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我痛单,道長(zhǎng)嘿棘,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任旭绒,我火速辦了婚禮鸟妙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘挥吵。我一直安慰自己重父,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布忽匈。 她就那樣靜靜地躺著房午,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丹允。 梳的紋絲不亂的頭發(fā)上郭厌,一...
    開(kāi)封第一講書(shū)人閱讀 49,792評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音雕蔽,去河邊找鬼折柠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛批狐,可吹牛的內(nèi)容都是我干的液走。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼缘眶!你這毒婦竟也來(lái)了嘱根?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤巷懈,失蹤者是張志新(化名)和其女友劉穎该抒,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體顶燕,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凑保,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了涌攻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欧引。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖恳谎,靈堂內(nèi)的尸體忽然破棺而出芝此,到底是詐尸還是另有隱情,我是刑警寧澤因痛,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布婚苹,位于F島的核電站,受9級(jí)特大地震影響鸵膏,放射性物質(zhì)發(fā)生泄漏膊升。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一谭企、第九天 我趴在偏房一處隱蔽的房頂上張望廓译。 院中可真熱鬧,春花似錦债查、人聲如沸责循。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)院仿。三九已至,卻和暖如春速和,著一層夾襖步出監(jiān)牢的瞬間歹垫,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工颠放, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留排惨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓碰凶,卻偏偏與公主長(zhǎng)得像暮芭,于是被迫代替她去往敵國(guó)和親鹿驼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

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