阿里为什么推荐使用LongAdder,而不是volatile?
這是我的第?87?篇原創(chuàng)文章
作者 | 王磊
來(lái)源 | Java中文社群(ID:javacn666)
轉(zhuǎn)載請(qǐng)聯(lián)系授權(quán)(微信ID:GG_Stone)
阿里《Java開(kāi)發(fā)手冊(cè)》最新嵩山版在 8.3 日發(fā)布,其中有一段內(nèi)容引起了老王的注意,內(nèi)容如下:
【參考】volatile 解決多線(xiàn)程內(nèi)存不可見(jiàn)問(wèn)題。對(duì)于一寫(xiě)多讀,是可以解決變量同步問(wèn)題,但是如果多寫(xiě),同樣無(wú)法解決線(xiàn)程安全問(wèn)題。
說(shuō)明:如果是 count++ 操作,使用如下類(lèi)實(shí)現(xiàn):AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推薦使用 LongAdder 對(duì)象,比 AtomicLong 性能更好(減少樂(lè)觀 鎖的重試次數(shù))。
以上內(nèi)容共有兩個(gè)重點(diǎn):
類(lèi)似于 count++ 這種非一寫(xiě)多讀的場(chǎng)景不能使用 volatile;
如果是 JDK8 推薦使用 LongAdder 而非 AtomicLong 來(lái)替代 volatile,因?yàn)?LongAdder 的性能更好。
但口說(shuō)無(wú)憑,即使是孤盡大佬說(shuō)的,咱們也得證實(shí)一下,因?yàn)轳R老爺子說(shuō)過(guò):實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)。
這樣做也有它的好處,第一,加深了我們對(duì)知識(shí)的認(rèn)知;第二,文檔上只寫(xiě)了LongAdder 比 AtomicLong 的性能高,但是高多少呢?文中并沒(méi)有說(shuō),那只能我們自己動(dòng)手去測(cè)試嘍。
話(huà)不多,接下來(lái)我們直接進(jìn)入本文正式內(nèi)容...
volatile 線(xiàn)程安全測(cè)試
首先我們來(lái)測(cè)試 volatile 在多寫(xiě)環(huán)境下的線(xiàn)程安全情況,測(cè)試代碼如下:
public?class?VolatileExample?{public?static?volatile?int?count?=?0;?//?計(jì)數(shù)器public?static?final?int?size?=?100000;?//?循環(huán)測(cè)試次數(shù)public?static?void?main(String[]?args)?{//?++?方式?10w?次Thread?thread?=?new?Thread(()?->?{for?(int?i?=?1;?i?<=?size;?i++)?{count++;}});thread.start();//?--?10w?次for?(int?i?=?1;?i?<=?size;?i++)?{count--;}//?等所有線(xiàn)程執(zhí)行完成while?(thread.isAlive())?{}System.out.println(count);?//?打印結(jié)果} }我們把 volatile 修飾的 count 變量 ++ 10w 次,在啟動(dòng)另一個(gè)線(xiàn)程 -- 10w 次,正常來(lái)說(shuō)結(jié)果應(yīng)該是 0,但是我們執(zhí)行的結(jié)果卻為:
1063
結(jié)論:由以上結(jié)果可以看出 volatile 在多寫(xiě)環(huán)境下是非線(xiàn)程安全的,測(cè)試結(jié)果和《Java開(kāi)發(fā)手冊(cè)》相吻合。
LongAdder VS AtomicLong
接下來(lái),我們使用 Oracle 官方的 JMH(Java Microbenchmark Harness, JAVA 微基準(zhǔn)測(cè)試套件)來(lái)測(cè)試一下兩者的性能,測(cè)試代碼如下:
import?org.openjdk.jmh.annotations.*; import?org.openjdk.jmh.infra.Blackhole; import?org.openjdk.jmh.runner.Runner; import?org.openjdk.jmh.runner.RunnerException; import?org.openjdk.jmh.runner.options.Options; import?org.openjdk.jmh.runner.options.OptionsBuilder;import?java.util.concurrent.TimeUnit; import?java.util.concurrent.atomic.AtomicInteger; import?java.util.concurrent.atomic.LongAdder;@BenchmarkMode(Mode.AverageTime)?//?測(cè)試完成時(shí)間 @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations?=?1,?time?=?1,?timeUnit?=?TimeUnit.SECONDS)?//?預(yù)熱?1?輪,每次?1s @Measurement(iterations?=?5,?time?=?5,?timeUnit?=?TimeUnit.SECONDS)?//?測(cè)試?5?輪,每次?3s @Fork(1)?//?fork?1?個(gè)線(xiàn)程 @State(Scope.Benchmark) @Threads(1000)?//?開(kāi)啟?1000?個(gè)并發(fā)線(xiàn)程 public?class?AlibabaAtomicTest?{public?static?void?main(String[]?args)?throws?RunnerException?{//?啟動(dòng)基準(zhǔn)測(cè)試Options?opt?=?new?OptionsBuilder().include(AlibabaAtomicTest.class.getSimpleName())?//?要導(dǎo)入的測(cè)試類(lèi).build();new?Runner(opt).run();?//?執(zhí)行測(cè)試}@Benchmarkpublic?int?atomicTest(Blackhole?blackhole)?throws?InterruptedException?{AtomicInteger?atomicInteger?=?new?AtomicInteger();for?(int?i?=?0;?i?<?1024;?i++)?{atomicInteger.addAndGet(1);}//?為了避免?JIT?忽略未被使用的結(jié)果return?atomicInteger.intValue();}@Benchmarkpublic?int?longAdderTest(Blackhole?blackhole)?throws?InterruptedException?{LongAdder?longAdder?=?new?LongAdder();for?(int?i?=?0;?i?<?1024;?i++)?{longAdder.add(1);}return?longAdder.intValue();} }程序執(zhí)行的結(jié)果為:
從上述的數(shù)據(jù)可以看出,在開(kāi)啟了 1000 個(gè)線(xiàn)程之后,程序的 LongAdder 的性能比 AtomicInteger 快了約 1.53 倍,你沒(méi)看出是開(kāi)了 1000 個(gè)線(xiàn)程,為什么要開(kāi)這么多呢?這其實(shí)是為了模擬高并發(fā)高競(jìng)爭(zhēng)的環(huán)境下二者的性能查詢(xún)。
如果在低競(jìng)爭(zhēng)下,比如我們開(kāi)啟 100 個(gè)線(xiàn)程,測(cè)試的結(jié)果如下:
結(jié)論:從上面結(jié)果可以看出,在低競(jìng)爭(zhēng)的并發(fā)環(huán)境下 AtomicInteger 的性能是要比 LongAdder 的性能好,而高競(jìng)爭(zhēng)環(huán)境下 LongAdder 的性能比 AtomicInteger 好,當(dāng)有 1000 個(gè)線(xiàn)程運(yùn)行時(shí),LongAdder 的性能比 AtomicInteger 快了約 1.53 倍,所以各位要根據(jù)自己業(yè)務(wù)情況選擇合適的類(lèi)型來(lái)使用。
性能分析
為什么會(huì)出現(xiàn)上面的情況?這是因?yàn)?AtomicInteger 在高并發(fā)環(huán)境下會(huì)有多個(gè)線(xiàn)程去競(jìng)爭(zhēng)一個(gè)原子變量,而始終只有一個(gè)線(xiàn)程能競(jìng)爭(zhēng)成功,而其他線(xiàn)程會(huì)一直通過(guò) CAS 自旋嘗試獲取此原子變量,因此會(huì)有一定的性能消耗;而 LongAdder 會(huì)將這個(gè)原子變量分離成一個(gè) Cell 數(shù)組,每個(gè)線(xiàn)程通過(guò) Hash 獲取到自己數(shù)組,這樣就減少了樂(lè)觀鎖的重試次數(shù),從而在高競(jìng)爭(zhēng)下獲得優(yōu)勢(shì);而在低競(jìng)爭(zhēng)下表現(xiàn)的又不是很好,可能是因?yàn)樽约罕旧頇C(jī)制的執(zhí)行時(shí)間大于了鎖競(jìng)爭(zhēng)的自旋時(shí)間,因此在低競(jìng)爭(zhēng)下表現(xiàn)性能不如 AtomicInteger。
總結(jié)
本文我們測(cè)試了 volatile 在多寫(xiě)情況下是非線(xiàn)程安全的,而在低競(jìng)爭(zhēng)的并發(fā)環(huán)境下 AtomicInteger 的性能是要比 LongAdder 的性能好,而高競(jìng)爭(zhēng)環(huán)境下 LongAdder 的性能比 AtomicInteger 好,因此我們?cè)谑褂脮r(shí)要結(jié)合自身的業(yè)務(wù)情況來(lái)選擇相應(yīng)的類(lèi)型。
阿里《Java開(kāi)發(fā)手冊(cè)》最新嵩山版發(fā)布!
6種快速統(tǒng)計(jì)代碼執(zhí)行時(shí)間的方法,真香!(史上最全)
Oracle官方推薦的性能測(cè)試工具!簡(jiǎn)單、精準(zhǔn)又直觀!
文末福利:我整理了一份 280 多頁(yè)的《JAVA核心面試知識(shí)整理.pdf》,包含了:Java 集合、Java 基礎(chǔ)、JVM、并發(fā)編程、Spring 原理、Netty、網(wǎng)絡(luò)、Kafka、Zookeeper、RabbitMQ、設(shè)計(jì)模式、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)和算法等面試題。
下載方式
1.?首先掃描下方二維碼
2.?后臺(tái)回復(fù)「面試」即可獲取
注明:僅僅作為知識(shí)分享,切勿用于其它商業(yè)活動(dòng) 。感謝所有技術(shù)分享者的付出。
總結(jié)
以上是生活随笔為你收集整理的阿里为什么推荐使用LongAdder,而不是volatile?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 磊哥私藏书单分享,160买400的书!
- 下一篇: 2018年终总结—努力做一个有趣的人