新聞中心
?這篇文章給大家聊一下java并發(fā)包下的CAS相關(guān)的原子操作,以及Java 8如何改進(jìn)和優(yōu)化CAS操作的性能。

淮北網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),淮北網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為淮北成百上千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營(yíng)銷網(wǎng)站建設(shè)要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的淮北做網(wǎng)站的公司定做!
因?yàn)锳tomic系列的原子類,無(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ō)下面這段代碼:
實(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ò)加鎖的方式變成線程安全的:
這個(gè)時(shí)候,代碼就是線程安全的了,因?yàn)槲覀兗恿藄ynchronized,也就是讓每個(gè)線程要進(jìn)入increment()方法之前先得嘗試加鎖,同一時(shí)間只有一個(gè)線程能加鎖,其他線程需要等待鎖。
通過(guò)這樣處理,就可以保證換個(gè)data每次都會(huì)累加1,不會(huì)出現(xiàn)數(shù)據(jù)錯(cuò)亂的問(wèn)題。
老規(guī)矩!我們來(lái)看看下面的圖,感受一下synchronized加鎖下的效果和氛圍,相當(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原子類及其底層原理
對(duì)于這種簡(jiǎn)單的data++類的操作,其實(shí)我們完全可以換一種做法,java并發(fā)包下面提供了一系列的Atomic原子類,比如說(shuō)AtomicInteger。
他可以保證多線程并發(fā)安全的情況下,高性能的并發(fā)更新一個(gè)數(shù)值。我們來(lái)看下面的代碼:
大家看上面的代碼,是不是很簡(jiǎn)單!多個(gè)線程可以并發(fā)的執(zhí)行AtomicInteger的incrementAndGet()方法,意思就是給我把data的值累加1,接著返回累加后最新的值。
這個(gè)代碼里,就沒(méi)有看到加鎖和釋放鎖這一說(shuō)了吧!
實(shí)際上,Atomic原子類底層用的不是傳統(tǒng)意義的鎖機(jī)制,而是無(wú)鎖化的CAS機(jī)制,通過(guò)CAS機(jī)制保證多線程修改一個(gè)數(shù)值的安全性
那什么是CAS呢?他的全稱是:Compare and Set,也就是先比較再設(shè)置的意思。
話不多說(shuō),先上圖!
我們來(lái)看上面的圖,假如說(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操作!
好!現(xiàn)在我們對(duì)照著上面的圖,來(lái)看一下這整個(gè)過(guò)程:
- 首先第一步,我們假設(shè)線程一咔嚓一下過(guò)來(lái)了,然后對(duì)AtomicInteger執(zhí)行incrementAndGet()操作,他底層就會(huì)先獲取AtomicInteger當(dāng)前的值,這個(gè)值就是0。
- 此時(shí)沒(méi)有別的線程跟他搶!他也不管那么多,直接執(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?。?/p>
線程3此時(shí)會(huì)問(wèn)問(wèn)說(shuō):兄弟,你現(xiàn)在值還是1嗎?
噩耗傳來(lái)?。。?nbsp;這個(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原子類的原理,沒(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è)新的類,LongAdder,他就是嘗試使用分段CAS以及自動(dòng)分段遷移的方式來(lái)大幅度提升多線程高并發(fā)執(zhí)行CAS操作的性能!
在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)返回給你。
文章標(biāo)題:Java8中的LongAdder類,大大提升CAS性能
分享網(wǎng)址:http://m.fisionsoft.com.cn/article/dhdgeho.html


咨詢
建站咨詢
