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