本文轉(zhuǎn)載自占小狼
如果讓你實(shí)現(xiàn)一個(gè)計(jì)數(shù)器,有點(diǎn)經(jīng)驗(yàn)的同學(xué)可以很快的想到使用AtomicInteger或者AtomicLong進(jìn)行簡單的封裝。
因?yàn)橛?jì)數(shù)器操作涉及到內(nèi)存的可見性和線程之間的競(jìng)爭,而Atomic*的實(shí)現(xiàn)完美的屏蔽了這些技術(shù)細(xì)節(jié)肤频,我們只需要執(zhí)行相應(yīng)的方法,就能實(shí)現(xiàn)對(duì)應(yīng)的業(yè)務(wù)需求。
Atomic雖然好用梗顺,不過這些的操作在并發(fā)量很大的情況下,性能問題也會(huì)被相應(yīng)的放大车摄。我們可以先看下其中getAndIncrement
的實(shí)現(xiàn)代碼
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
// unsafe類中的實(shí)現(xiàn)
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
很顯然寺谤,在getAndAddLong
實(shí)現(xiàn)中仑鸥,為了實(shí)現(xiàn)正確的累加操作,如果并發(fā)量很大的話变屁,cpu會(huì)花費(fèi)大量的時(shí)間在試錯(cuò)上面眼俊,相當(dāng)于一個(gè)spin的操作。如果并發(fā)量小的情況粟关,這些消耗可以忽略不計(jì)泵琳。
既然已經(jīng)意識(shí)到Atomic***有這樣的業(yè)務(wù)缺陷,Doug Lea大神又給我們提供了LongAdder誊役,內(nèi)部的實(shí)現(xiàn)有點(diǎn)類似ConcurrentHashMap的分段鎖获列,最好的情況下,每個(gè)線程都有獨(dú)立的計(jì)數(shù)器蛔垢,這樣可以大量減少并發(fā)操作击孩。
下面通過JMH比較一下AtomicLong 和 LongAdder的性能。
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@BenchmarkMode(Mode.Throughput)
public class Main {
private static AtomicLong count = new AtomicLong();
private static LongAdder longAdder = new LongAdder();
public static void main(String[] args) throws Exception {
Options options = new OptionsBuilder().include(Main.class.getName()).forks(1).build();
new Runner(options).run();
}
@Benchmark
@Threads(10)
public void run0(){
count.getAndIncrement();
}
@Benchmark
@Threads(10)
public void run1(){
longAdder.increment();
}
}
1鹏漆、設(shè)置BenchmarkMode為Mode.Throughput巩梢,測(cè)試吞吐量
2、設(shè)置BenchmarkMode為Mode.AverageTime艺玲,測(cè)試平均耗時(shí)
線程數(shù)為1
1括蝠、吞吐量
Benchmark Mode Cnt Score Error Units
Main.run0 thrpt 5 154.525 ± 9.767 ops/us
Main.run1 thrpt 5 89.599 ± 7.951 ops/us
2、平均耗時(shí)
Benchmark Mode Cnt Score Error Units
Main.run0 avgt 5 0.007 ± 0.001 us/op
Main.run1 avgt 5 0.011 ± 0.001 us/op
單線程情況:
1饭聚、AtomicLong的吞吐量和平均耗時(shí)都占優(yōu)勢(shì)
線程數(shù)為10
1忌警、吞吐量
Benchmark Mode Cnt Score Error Units
Main.run0 thrpt 5 37.780 ± 1.891 ops/us
Main.run1 thrpt 5 464.927 ± 143.207 ops/us
2、平均耗時(shí)
Benchmark Mode Cnt Score Error Units
Main.run0 avgt 5 0.290 ± 0.038 us/op
Main.run1 avgt 5 0.021 ± 0.001 us/op
并發(fā)線程為10個(gè)時(shí):
LongAdder的吞吐量比較大秒梳,是AtomicLong的10倍多法绵。
LongAdder的平均耗時(shí)是AtomicLong的十分之一。
線程數(shù)為30
1酪碘、吞吐量
Benchmark Mode Cnt Score Error Units
Main.run0 thrpt 5 36.215 ± 2.341 ops/us
Main.run1 thrpt 5 486.630 ± 26.894 ops/us
2朋譬、平均耗時(shí)
Main.run0 avgt 5 0.792 ± 0.021 us/op
Main.run1 avgt 5 0.063 ± 0.002 us/op
線程數(shù)為30個(gè)時(shí):
- LongAdder的吞吐量比較大,也是AtomicLong的10倍多兴垦。
- LongAdder的平均耗時(shí)也是AtomicLong的十分之一徙赢。
總結(jié)
一些高并發(fā)的場(chǎng)景,比如限流計(jì)數(shù)器探越,建議使用LongAdder替換AtomicLong狡赐,性能可以提升不少。
關(guān)注我扶关,這里只有干貨阴汇!