在并發程式設計時,經常需要進行計數,如統計處理的記錄條數、成功處理的條數、失敗處理的條數等,本文針對synchronized、LongAdder、LongAccumulator、AtomicLong四種方案實作的計數器進行性能對比,并給出使用建議。
1. 計數器實作
1.1. 實作方案及測試結果
分别使用synchronized、AtomicLong、LongAdder、LongAccumulator方案,實作計數器,實作代碼參見下面的代碼。
性能測試結果詳見下面的輸出結果,LongAdder和LongAccumulator性能相當,synchronized性能最差,AtomicLong的耗時約為synchronized一半,LongAdder和LongAccumulator的耗時約為前兩個的幾十分之一。
1.2. 結果分析
AtomicLong是JDK1.5開始出現的,裡面主要使用了一個long類型的value作為成員變量,然後使用循環的CAS操作去操作value的值,并發量比較大的情況下,CAS操作失敗的機率較高,内部失敗了會重試,導緻耗時可能會增加。
LongAdder是JDK1.8開始出現的,所提供的API基本上可以替換掉原先的AtomicLong。LongAdder在并發量比較大的情況下,操作資料的時候,相當于把這個數字分成了很多份數字,然後交給多個人去管控,每個管控者負責保證部分數字在多線程情況下操作的正确性。當多線程通路的時,通過hash算法映射到具體管控者去操作資料,最後再彙總所有的管控者的資料,得到最終結果。相當于降低了并發情況下鎖的粒度,是以效率比較高,看一下下面的圖,友善了解:
LongAccumulator是LongAdder的功能增強版。LongAdder的API隻有對數值的加減,而LongAccumulator提供了自定義的函數操作,其構造函數如下:
/**
* Creates a new instance using the given accumulator function
* and identity element.
* @param accumulatorFunction a side-effect-free function of two arguments
* @param identity identity (initial value) for the accumulator function
*/
public LongAccumulator(LongBinaryOperator accumulatorFunction,
long identity) {
this.function = accumulatorFunction;
base = this.identity = identity;
}
LongAdder、LongAccumulator全面超越同步鎖及AtomicLong的方式,建議在使用AtomicLong的地方可以直接替換為LongAdder、LongAccumulator,吞吐量更高一些
2. 計數器不同的實作方案
模拟50個線程,每個線程對計數器遞增100萬次,最終結果應該是5000萬。
分别使用synchronized、LongAdder、LongAccumulator、AtomicLong四種實作方案,并進行性能測試。
2.1 實作代碼
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
public class CounterTest {
private static ICounter counter;
public static void main(String[] args) throws Exception {
for (int j = 1; j < 5; j++) {
counter = CounterFactory.getCounter(j);
System.out.println(String.format("基于%s進行計數", counter.getClass().getSimpleName()));
for (int i = 0; i < 10; i++) {
counter.reset();
count(counter);
}
}
}
private static void count(ICounter counter) throws InterruptedException {
long t1 = System.currentTimeMillis();
int threadCount = 50;
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
try {
for (int j = 0; j < 1000000; j++) {
counter.incr();
}
} finally {
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
long t2 = System.currentTimeMillis();
System.out.println(String.format("結果:%s,耗時(ms):%s", counter.get(), (t2 - t1)));
}
}
interface ICounter {
public void reset();
public void incr();
public long get();
}
class SynchronizedCounter implements ICounter {
private Integer count = 0;
private Object lock = new Object();
@Override
public void reset() {
count = 0;
}
@Override
public void incr() {
synchronized (lock) {
count++;
}
}
@Override
public long get() {
return count;
}
}
class AtomicLongCounter implements ICounter {
private AtomicLong count = new AtomicLong(0);
@Override
public void reset() {
count.set(0);
}
@Override
public void incr() {
count.incrementAndGet();
}
@Override
public long get() {
return count.get();
}
}
class LongAdderCounter implements ICounter {
private LongAdder count = new LongAdder();
@Override
public void reset() {
count.reset();
}
@Override
public void incr() {
count.increment();
}
@Override
public long get() {
return count.sum();
}
}
class LongAccumulatorCounter implements ICounter {
private LongAccumulator count = new LongAccumulator((x, y) -> x + y, 0L);
@Override
public void reset() {
count.reset();
}
@Override
public void incr() {
count.accumulate(1);
}
@Override
public long get() {
return count.longValue();
}
}
class CounterFactory {
public static ICounter getCounter(int counterType) throws Exception {
switch (counterType) {
case 1:
return new SynchronizedCounter();
case 2:
return new AtomicLongCounter();
case 3:
return new LongAdderCounter();
case 4:
return new LongAccumulatorCounter();
}
throw new Exception("參數錯誤");
}
}
2.2. 輸出結果
基于SynchronizedCounter進行計數
結果:50000000,耗時(ms):1451
結果:50000000,耗時(ms):1452
結果:50000000,耗時(ms):1397
結果:50000000,耗時(ms):1406
結果:50000000,耗時(ms):1408
結果:50000000,耗時(ms):1411
結果:50000000,耗時(ms):1422
結果:50000000,耗時(ms):1415
結果:50000000,耗時(ms):1434
結果:50000000,耗時(ms):1430
基于AtomicLongCounter進行計數
結果:50000000,耗時(ms):777
結果:50000000,耗時(ms):752
結果:50000000,耗時(ms):756
結果:50000000,耗時(ms):768
結果:50000000,耗時(ms):757
結果:50000000,耗時(ms):761
結果:50000000,耗時(ms):769
結果:50000000,耗時(ms):784
結果:50000000,耗時(ms):776
結果:50000000,耗時(ms):772
基于LongAdderCounter進行計數
結果:50000000,耗時(ms):60
結果:50000000,耗時(ms):53
結果:50000000,耗時(ms):54
結果:50000000,耗時(ms):52
結果:50000000,耗時(ms):55
結果:50000000,耗時(ms):51
結果:50000000,耗時(ms):53
結果:50000000,耗時(ms):55
結果:50000000,耗時(ms):51
結果:50000000,耗時(ms):51
基于LongAccumulatorCounter進行計數
結果:50000000,耗時(ms):55
結果:50000000,耗時(ms):51
結果:50000000,耗時(ms):52
結果:50000000,耗時(ms):51
結果:50000000,耗時(ms):52
結果:50000000,耗時(ms):52
結果:50000000,耗時(ms):51
結果:50000000,耗時(ms):51
結果:50000000,耗時(ms):51
結果:50000000,耗時(ms):50