LongAdder是jdk 1.8引入的一個類,宣稱比AtomiLong更高效。它內部有一個基本數base和一個cell數組,在高併發的情況下各個線程將值存放在了數組中,在低併發的情況下直接在一個base數上做計算,取值的時候把基本數和cell數組中的值做累加返回。LongAdder採用了類似分段鎖的設計,降低了競爭。
其實看到取值的時候我是懵的,取值的代碼如下:
public long sum() {
Cell[] as = cells; Cell a;
long sum = base;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
註釋裏說了,當沒有併發更新的時候,這個值是準確的。當有併發更新的時候,這個值就不準確了。原因很簡單,雖然base和cells都是volatile類型,保證了可見性,然而我累加到一半的時候,base因爲併發更新而改變了,可我已經累加過了,拿到的結果肯定沒有包括這期間新增的值。
令人沮喪的是,在併發期間,這個真實的值幾乎是不可用的。雖然LongAdder存起來了各個確定的值,但是我卻拿不到這個準確的和。
因此LongAdder,應該只適用於對計數不要求那麼精確(雖然它是精確的保存了所有值的),或者只關心最終的值(併發已經結束,可以拿到最終的結果)。在阿里巴巴JAVA開發手冊中,稍微提到了LongAdder在做類似count++操作方面的優勢,如果只是想統計下最後的個數,那麼LongAdder肯定是最好的選擇。
下面附上LongAdder和AtomicLong在做count++上面的比較,測試代碼如下:
@Data
@Slf4j
public class LongAdderTest{
public static final int COR = 1000;
public static final int MAX = 10000;
public static final int THREAD_COUNT = 1000; // 通過修改併發線程數和循環次數來測試
public static final int LOOP = 1000000; // 通過修改併發線程數和循環次數來測試
public static void main(String[] args) throws Exception{
ExecutorService pool = new ThreadPoolExecutor(COR,MAX,60L, TimeUnit.SECONDS,new LinkedBlockingQueue<>());
final LongAdder adder = new LongAdder();
final AtomicLong count = new AtomicLong();
CompletionService completionService = new ExecutorCompletionService<>(pool);
long start = System.currentTimeMillis();
for(int i =0;i<THREAD_COUNT;i++){
completionService.submit(() -> {
for (int j = 0; j < LOOP; j++) {
//count.incrementAndGet();
adder.increment();
}
return 1L;
});
}
for (int i = 0; i < THREAD_COUNT; i++) {
Future<Long> future = completionService.take();
future.get();
}
System.out.println("耗時:" + (System.currentTimeMillis() - start) + "ms");
pool.shutdown();
}
我們固定循環100W次,然後改變併發線程數,下面是測試結果:
10 | 100 | 1000 | |
---|---|---|---|
AtomicLong | 441ms | 2.931s | 28.851s |
LongAdder | 203ms | 706ms | 6.526s |
可以看到,在低併發的時候兩者區別並不是很大,在高併發的時候兩者耗時就不是一個數量級了。