併發效能優化之使用LongAdder替換AtomicLong

LongAdder
本文轉載自佔小狼
如果讓你實現一個計數器,有點經驗的同學可以很快的想到使用AtomicInteger或者AtomicLong進行簡單的封裝。
的實現完美的遮蔽了這些技術細節,我們只需要執行相應的方法,就能實現對應的業務需求。
Atomic
雖然好用,不過這些的操作在併發量很大的情況下,效能問題也會被相應的放大。我們可以先看下其中getAndIncrement
的實現程式碼
public final long getAndIncrement() { return unsafe.getAndAddLong(this, valueOffset, 1L); } // unsafe類中的實現 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
實現中,為了實現正確的累加操作,如果併發量很大的話,cpu會花費大量的時間在試錯上面,相當於一個spin的操作。如果併發量小的情況,這些消耗可以忽略不計。
既然已經意識到Atomic***有這樣的業務缺陷,Doug Lea大神又給我們提供了LongAdder,內部的實現有點類似ConcurrentHashMap的分段鎖,最好的情況下,每個執行緒都有獨立的計數器,這樣可以大量減少併發操作。
下面通過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、設定BenchmarkMode為Mode.Throughput,測試吞吐量
2、設定BenchmarkMode為Mode.AverageTime,測試平均耗時
執行緒數為1
1、吞吐量
BenchmarkModeCntScoreErrorUnits Main.run0thrpt5154.525 ± 9.767ops/us Main.run1thrpt589.599 ± 7.951ops/us
2、平均耗時
BenchmarkModeCntScoreErrorUnits Main.run0avgt50.007 ±0.001us/op Main.run1avgt50.011 ±0.001us/op
單執行緒情況:
1、AtomicLong的吞吐量和平均耗時都佔優勢
執行緒數為10
1、吞吐量
BenchmarkModeCntScoreErrorUnits Main.run0thrpt537.780 ±1.891ops/us Main.run1thrpt5464.927 ± 143.207ops/us
2、平均耗時
BenchmarkModeCntScoreErrorUnits Main.run0avgt50.290 ± 0.038us/op Main.run1avgt50.021 ± 0.001us/op
併發執行緒為10個時:
LongAdder的吞吐量比較大,是AtomicLong的10倍多。
LongAdder的平均耗時是AtomicLong的十分之一。
執行緒數為30
1、吞吐量
BenchmarkModeCntScoreErrorUnits Main.run0thrpt536.215 ±2.341ops/us Main.run1thrpt5486.630 ± 26.894ops/us
2、平均耗時
Main.run0avgt50.792 ± 0.021us/op Main.run1avgt50.063 ± 0.002us/op
執行緒數為30個時:
- LongAdder的吞吐量比較大,也是AtomicLong的10倍多。
- LongAdder的平均耗時也是AtomicLong的十分之一。
總結
一些高併發的場景,比如限流計數器,建議使用LongAdder替換AtomicLong,效能可以提升不少。
關注我,這裡只有乾貨!