Java面試:并發(fā)常見(jiàn)問(wèn)題之CAS

查看原文請(qǐng)點(diǎn)擊:原文地址

一熙涤、前情回顧
上篇文章給大家聊了一下volatile的原理,具體參見(jiàn):《大白話聊聊Java并發(fā)面試問(wèn)題之volatile到底是什么饱溢?》庭猩。
這篇文章給大家聊一下java并發(fā)包下的CAS相關(guān)的原子操作,以及Java 8如何改進(jìn)和優(yōu)化CAS操作的性能纸泡。
因?yàn)锳tomic系列的原子類(lèi)漂问,無(wú)論在并發(fā)編程、JDK源碼女揭、還是各種開(kāi)源項(xiàng)目中蚤假,都經(jīng)常用到。而且在Java并發(fā)面試中吧兔,這一塊也屬于比較高頻的考點(diǎn)磷仰,所以還是值得給大家聊一聊。

二境蔼、場(chǎng)景引入灶平,問(wèn)題凸現(xiàn)
好伺通,我們正式開(kāi)始!假設(shè)多個(gè)線程需要對(duì)一個(gè)變量不停的累加1逢享,比如說(shuō)下面這段代碼:

image.png

實(shí)際上罐监,上面那段代碼是不ok的,因?yàn)槎鄠€(gè)線程直接這樣并發(fā)的對(duì)一個(gè)data變量進(jìn)行修改瞒爬,是線程不安全性的行為弓柱,會(huì)導(dǎo)致data值的變化不遵照預(yù)期的值來(lái)改變。

舉個(gè)例子侧但,比如說(shuō)20個(gè)線程分別對(duì)data執(zhí)行一次data++操作矢空,我們以為最后data的值會(huì)變成20,其實(shí)不是禀横。
最后可能data的值是18妇多,或者是19,都有可能燕侠,因?yàn)槎嗑€程并發(fā)操作下者祖,就是會(huì)有這種安全問(wèn)題,導(dǎo)致數(shù)據(jù)結(jié)果不準(zhǔn)確绢彤。
至于為什么會(huì)不準(zhǔn)確七问?那不在本文討論的范圍里,因?yàn)檫@個(gè)一般只要是學(xué)過(guò)java的同學(xué)茫舶,肯定都了解過(guò)多線程并發(fā)問(wèn)題械巡。

三、初步的解決方案:synchronized
所以饶氏,對(duì)于上面的代碼讥耗,一般我們會(huì)改造一下,讓他通過(guò)加鎖的方式變成線程安全的:

image.png

這個(gè)時(shí)候疹启,代碼就是線程安全的了古程,因?yàn)槲覀兗恿?strong>synchronized,也就是讓每個(gè)線程要進(jìn)入increment()方法之前先得嘗試加鎖喊崖,同一時(shí)間只有一個(gè)線程能加鎖挣磨,其他線程需要等待鎖。
通過(guò)這樣處理荤懂,就可以保證換個(gè)data每次都會(huì)累加1茁裙,不會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)亂的問(wèn)題。但是相當(dāng)于N個(gè)線程一個(gè)一個(gè)的排隊(duì)在更新那個(gè)數(shù)值节仿。
而且如此簡(jiǎn)單的data++操作晤锥,都要加一個(gè)重磅的synchronized鎖來(lái)解決多線程并發(fā)問(wèn)題,就有點(diǎn)殺雞用牛刀廊宪,大材小用了矾瘾。
雖然隨著Java版本更新眉踱,也對(duì)synchronized做了很多優(yōu)化,但是處理這種簡(jiǎn)單的累加操作霜威,仍然顯得“太重了”谈喳。人家synchronized是可以解決更加復(fù)雜的并發(fā)編程場(chǎng)景和問(wèn)題的。
而且戈泼,在這個(gè)場(chǎng)景下婿禽,你要是用synchronized,不就相當(dāng)于讓各個(gè)線程串行化了么大猛?一個(gè)接一個(gè)的排隊(duì)扭倾,加鎖,處理數(shù)據(jù)挽绩,釋放鎖膛壹,下一個(gè)再進(jìn)來(lái)。

四唉堪、更高效的方案:Atomic原子類(lèi)及其底層原理
對(duì)于這種簡(jiǎn)單的data++類(lèi)的操作模聋,其實(shí)我們完全可以換一種做法,java并發(fā)包下面提供了一系列的Atomic原子類(lèi)唠亚,比如說(shuō)AtomicInteger链方。
他可以保證多線程并發(fā)安全的情況下,高性能的并發(fā)更新一個(gè)數(shù)值灶搜。我們來(lái)看下面的代碼:


image.png

大家看上面的代碼祟蚀,是不是很簡(jiǎn)單!多個(gè)線程可以并發(fā)的執(zhí)行AtomicInteger的incrementAndGet()方法割卖,意思就是給我把data的值累加1前酿,接著返回累加后最新的值。

這個(gè)代碼里鹏溯,就沒(méi)有看到加鎖和釋放鎖這一說(shuō)了吧罢维!

實(shí)際上,****Atomic原子類(lèi)底層用的不是傳統(tǒng)意義的鎖機(jī)制剿涮,而是無(wú)鎖化的CAS機(jī)制言津,通過(guò)CAS機(jī)制保證多線程修改一個(gè)數(shù)值的安全性

那什么是CAS呢攻人?他的全稱是:Compare and Set取试,也就是先比較再設(shè)置的意思。

假如說(shuō)有3個(gè)線程并發(fā)的要修改一個(gè)AtomicInteger的值怀吻,他們底層的機(jī)制如下:

首先瞬浓,每個(gè)線程都會(huì)先獲取當(dāng)前的值,接著走一個(gè)原子的CAS操作蓬坡,原子的意思就是這個(gè)CAS操作一定是自己完整執(zhí)行完的猿棉,不會(huì)被別人打斷磅叛。

然后CAS操作里,會(huì)比較一下說(shuō)萨赁,唉弊琴!大兄弟!現(xiàn)在你的值是不是剛才我獲取到的那個(gè)值罢人敲董?

如果是的話,bingo慰安!說(shuō)明沒(méi)人改過(guò)這個(gè)值腋寨,那你給我設(shè)置成累加1之后的一個(gè)值好了!

同理化焕,如果有人在執(zhí)行CAS的時(shí)候萄窜,發(fā)現(xiàn)自己之前獲取的值跟當(dāng)前的值不一樣,會(huì)導(dǎo)致CAS失敗撒桨,失敗之后查刻,進(jìn)入一個(gè)無(wú)限循環(huán),再次獲取值凤类,接著執(zhí)行CAS操作赖阻!

  • 首先第一步,我們假設(shè)線程一咔嚓一下過(guò)來(lái)了踱蠢,然后對(duì)AtomicInteger執(zhí)行incrementAndGet()操作火欧,他底層就會(huì)先獲取AtomicInteger當(dāng)前的值,這個(gè)值就是0茎截。

  • 此時(shí)沒(méi)有別的線程跟他搶?zhuān)∷膊还苣敲炊辔郑苯訄?zhí)行原子的CAS操作,問(wèn)問(wèn)人家說(shuō):兄弟企锌,你現(xiàn)在值還是0嗎榆浓?

  • 如果是,說(shuō)明沒(méi)人修改過(guò)八涸堋陡鹃!太好了,給我累加1抖坪,設(shè)置為1萍鲸。于是AtomicInteger的值變?yōu)?!

  • 接著線程2和線程3同時(shí)跑了過(guò)來(lái)擦俐,因?yàn)榈讓硬皇腔阪i機(jī)制脊阴,都是無(wú)鎖化的CAS機(jī)制,所以他們倆可能會(huì)并發(fā)的同時(shí)執(zhí)行incrementAndGet()操作。

  • 然后倆人都獲取到了當(dāng)前AtomicInteger的值嘿期,就是1

  • 接著線程2搶先一步發(fā)起了原子的CAS操作品擎!注意,CAS是原子的备徐,此時(shí)就他一個(gè)線程在執(zhí)行萄传!

  • 然后線程2問(wèn):兄弟,你現(xiàn)在值還是1嗎蜜猾?如果是盲再,太好了,說(shuō)明沒(méi)人改過(guò)瓣铣,我來(lái)改成2

  • 好了答朋,此時(shí)AtomicInteger的值變?yōu)榱?。關(guān)鍵點(diǎn)來(lái)了:現(xiàn)在線程3接著發(fā)起了CAS操作棠笑,但是他手上還是拿著之前獲取到的那個(gè)1懊瓮搿!

  • 線程3此時(shí)會(huì)問(wèn)問(wèn)說(shuō):兄弟蓖救,你現(xiàn)在值還是1嗎洪规?

  • 噩耗傳來(lái)!Q唷斩例!這個(gè)時(shí)候的值是2啊从橘!線程3哭泣了念赶,他說(shuō),居然有人在這個(gè)期間改過(guò)值恰力。算了叉谜,那我還是重新再獲取一次值吧,于是獲取到了最新的值踩萎,值為2停局。

  • 然后再次發(fā)起CAS操作,問(wèn)問(wèn)香府,現(xiàn)在值是2嗎董栽?是的!太好了企孩,沒(méi)人改锭碳,我抓緊改,此時(shí)AtomicInteger值變?yōu)?柠硕!

上述整個(gè)過(guò)程工禾,就是所謂Atomic原子類(lèi)的原理运提,沒(méi)有基于加鎖機(jī)制串行化蝗柔,而是基于CAS機(jī)制:先獲取一個(gè)值闻葵,然后發(fā)起CAS,比較這個(gè)值被人改過(guò)沒(méi)癣丧?如果沒(méi)有槽畔,就更改值!這個(gè)CAS是原子的胁编,別人不會(huì)打斷你厢钧!

通過(guò)這個(gè)機(jī)制,不需要加鎖這么重量級(jí)的機(jī)制嬉橙,也可以用輕量級(jí)的方式實(shí)現(xiàn)多個(gè)線程安全的并發(fā)的修改某個(gè)數(shù)值早直。

五、Java 8對(duì)CAS機(jī)制的優(yōu)化

但是這個(gè)CAS有沒(méi)有問(wèn)題呢市框?肯定是有的霞扬。比如說(shuō)大量的線程同時(shí)并發(fā)修改一個(gè)AtomicInteger,可能有很多線程會(huì)不停的自旋枫振,進(jìn)入一個(gè)無(wú)限重復(fù)的循環(huán)中喻圃。

這些線程不停地獲取值,然后發(fā)起CAS操作粪滤,但是發(fā)現(xiàn)這個(gè)值被別人改過(guò)了斧拍,于是再次進(jìn)入下一個(gè)循環(huán),獲取值杖小,發(fā)起CAS操作又失敗了肆汹,再次進(jìn)入下一個(gè)循環(huán)。

在大量線程高并發(fā)更新AtomicInteger的時(shí)候予权,這種問(wèn)題可能會(huì)比較明顯县踢,導(dǎo)致大量線程空循環(huán),自旋轉(zhuǎn)伟件,性能和效率都不是特別好硼啤。

于是,當(dāng)當(dāng)當(dāng)當(dāng)斧账,Java 8推出了一個(gè)新的類(lèi)谴返,LongAdder,他就是嘗試使用分段CAS以及自動(dòng)分段遷移的方式來(lái)大幅度提升多線程高并發(fā)執(zhí)行CAS操作的性能咧织!

image

在LongAdder的底層實(shí)現(xiàn)中嗓袱,首先有一個(gè)base值,剛開(kāi)始多線程來(lái)不停的累加數(shù)值习绢,都是對(duì)base進(jìn)行累加的渠抹,比如剛開(kāi)始累加成了base = 5蝙昙。

接著如果發(fā)現(xiàn)并發(fā)更新的線程數(shù)量過(guò)多,就會(huì)開(kāi)始施行分段CAS的機(jī)制梧却,也就是內(nèi)部會(huì)搞一個(gè)Cell數(shù)組奇颠,每個(gè)數(shù)組是一個(gè)數(shù)值分段。

這時(shí)放航,讓大量的線程分別去對(duì)不同Cell內(nèi)部的value值進(jìn)行CAS累加操作烈拒,這樣就把CAS計(jì)算壓力分散到了不同的Cell分段數(shù)值中了!

這樣就可以大幅度的降低多線程并發(fā)更新同一個(gè)數(shù)值時(shí)出現(xiàn)的無(wú)限循環(huán)的問(wèn)題广鳍,大幅度提升了多線程并發(fā)更新數(shù)值的性能和效率荆几!

而且他內(nèi)部實(shí)現(xiàn)了自動(dòng)分段遷移的機(jī)制,也就是如果某個(gè)Cell的value執(zhí)行CAS失敗了赊时,那么就會(huì)自動(dòng)去找另外一個(gè)Cell分段內(nèi)的value值進(jìn)行CAS操作吨铸。

這樣也解決了線程空旋轉(zhuǎn)、自旋不停等待執(zhí)行CAS操作的問(wèn)題祖秒,讓一個(gè)線程過(guò)來(lái)執(zhí)行CAS時(shí)可以盡快的完成這個(gè)操作诞吱。

最后,如果你要從LongAdder中獲取當(dāng)前累加的總值狈涮,就會(huì)把base值和所有Cell分段數(shù)值加起來(lái)返回給你狐胎。

六、總結(jié) & 思考

不知道大家有沒(méi)有發(fā)現(xiàn)這種高并發(fā)訪問(wèn)下的分段處理機(jī)制歌馍,在很多地方都有類(lèi)似的思想體現(xiàn)握巢!因?yàn)楦卟l(fā)中的分段處理機(jī)制實(shí)際上是一個(gè)很常見(jiàn)和常用的并發(fā)優(yōu)化手段。

在我們之前的一篇講分布式鎖的文章:(《每秒上千訂單場(chǎng)景下的分布式鎖高并發(fā)優(yōu)化實(shí)踐》)松却,也是用到了分段加鎖以及自動(dòng)分段遷移/合并加鎖的一套機(jī)制暴浦,來(lái)大幅度幾十倍的提升分布式鎖的并發(fā)性能。

所以其實(shí)很多技術(shù)晓锻,思想都是有異曲同工之妙的歌焦。

END

如有收獲,請(qǐng)幫忙轉(zhuǎn)發(fā)砚哆,您的鼓勵(lì)是作者最大的動(dòng)力独撇,謝謝!

image

推薦閱讀:

  1. 微服務(wù)架構(gòu)如何保障雙11狂歡下的99.99%高可用

  2. 【性能優(yōu)化之道】每秒上萬(wàn)并發(fā)下的Spring Cloud參數(shù)優(yōu)化實(shí)戰(zhàn)

  3. 【雙11狂歡的背后】微服務(wù)注冊(cè)中心如何承載大型系統(tǒng)的千萬(wàn)級(jí)訪問(wèn)躁锁?

  4. 拜托纷铣!面試請(qǐng)不要再問(wèn)我Spring Cloud底層原理

  5. 【性能優(yōu)化的秘密】Hadoop如何將TB級(jí)大文件的上傳性能優(yōu)化上百倍?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末战转,一起剝皮案震驚了整個(gè)濱河市搜立,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌槐秧,老刑警劉巖啄踊,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忧设,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡颠通,警方通過(guò)查閱死者的電腦和手機(jī)址晕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蒜哀,“玉大人斩箫,你說(shuō)我怎么就攤上這事吏砂∧於” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵狐血,是天一觀的道長(zhǎng)淀歇。 經(jīng)常有香客問(wèn)我,道長(zhǎng)匈织,這世上最難降的妖魔是什么浪默? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮缀匕,結(jié)果婚禮上纳决,老公的妹妹穿的比我還像新娘。我一直安慰自己乡小,他們只是感情好阔加,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著满钟,像睡著了一般胜榔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上湃番,一...
    開(kāi)封第一講書(shū)人閱讀 51,462評(píng)論 1 302
  • 那天夭织,我揣著相機(jī)與錄音,去河邊找鬼吠撮。 笑死尊惰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泥兰。 我是一名探鬼主播弄屡,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼逾条!你這毒婦竟也來(lái)了琢岩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤师脂,失蹤者是張志新(化名)和其女友劉穎担孔,沒(méi)想到半個(gè)月后江锨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡糕篇,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年啄育,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拌消。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挑豌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墩崩,到底是詐尸還是另有隱情氓英,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布鹦筹,位于F島的核電站铝阐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏铐拐。R本人自食惡果不足惜徘键,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望遍蟋。 院中可真熱鬧吹害,春花似錦、人聲如沸虚青。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)挟憔。三九已至钟些,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绊谭,已是汗流浹背政恍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留达传,地道東北人篙耗。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宪赶,于是被迫代替她去往敵國(guó)和親宗弯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354