In concurrent programming, it is often necessary to count, such as the number of records processed, the number of successful processed, the number of failed processed, etc. This paper compares the performance of counters implemented by synchronized, LongAdder, LongAccumulator and AtomicLong, and gives some suggestions for use.
1. Counter implementation
1.1. Implementation scheme and test results
Use synchronized, AtomicLong, LongAdder and LongAccumulator schemes to implement counters respectively. See the following code for the implementation code.
The performance test results are shown in the following output results. LongAdder and LongAccumulator have the same performance, and synchronized performance is the worst. AtomicLong takes about half the time of synchronized, and LongAdder and LongAccumulator take about one tenth of the time of the first two.
1.2. Result analysis
AtomicLong is jdk1 5. It mainly uses a long type value as the member variable, and then uses the cyclic CAS operation to operate the value. When the concurrency is large, the probability of CAS operation failure is high, and the internal failure will be retried, resulting in increased time consumption.
LongAdder is jdk1 8 began to appear, and the API provided can basically replace the original AtomicLong. When LongAdder operates data with a large amount of concurrency, it is equivalent to dividing the number into many parts, and then giving it to multiple people for control. Each controller is responsible for ensuring the correctness of some numbers in the case of multithreading. When accessed by multiple threads, it is mapped to the specific controller to operate the data through the hash algorithm. Finally, it summarizes the data of all controllers to get the final result. It is equivalent to reducing the granularity of locks in the case of concurrency, so the efficiency is relatively high. Take a look at the following figure for easy understanding:
LongAccumulator is an enhanced version of LongAdder. The API of LongAdder only adds and subtracts values, while the LongAccumulator provides user-defined function operations, and its constructor is as follows:
/** * 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 and LongAccumulator completely surpass the way of synchronization lock and AtomicLong. It is suggested that LongAdder and LongAccumulator can be directly replaced where AtomicLong is used, with higher throughput
2. Different implementation schemes of counter
Simulate 50 threads, each thread increments the counter 1 million times, and the final result should be 50 million.
Four implementation schemes of synchronized, LongAdder, LongAccumulator and AtomicLong are used respectively, and the performance is tested.
2.1 implementation code
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("be based on%s Count", 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("result:%s,time consuming(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("Parameter error"); } }
2.2. Output results
Count based on synchronized counter
Result: 50 million, time consuming (ms): 1451
Result: 50 million, time consuming (ms): 1452
Result: 50000000, time consuming (ms): 1397
Result: 50 million, time consuming (ms): 1406
Result: 50 million, time consuming (ms): 1408
Result: 50 million, time consuming (ms): 1411
Result: 50 million, time consuming (ms): 1422
Result: 50 million, time consuming (ms): 1415
Result: 50 million, time consuming (ms): 1434
Result: 50 million, time consuming (ms): 1430
Counting based on AtomicLongCounter
Result: 50 million, time consuming (ms): 777
Result: 50 million, time consuming (ms): 752
Result: 50 million, time consuming (ms): 756
Result: 50 million, time consuming (ms): 768
Result: 50 million, time consuming (ms): 757
Result: 50 million, time consuming (ms): 761
Result: 50 million, time consuming (ms): 769
Result: 50 million, time consuming (ms): 784
Result: 50 million, time consuming (ms): 776
Result: 50 million, time consuming (ms): 772
Count based on LongAdderCounter
Result: 50 million, time consuming (ms): 60
Result: 50 million, time consuming (ms): 53
Result: 50 million, time consuming (ms): 54
Result: 50 million, time consuming (ms): 52
Result: 50 million, time consuming (ms): 55
Result: 50 million, time consuming (ms): 51
Result: 50 million, time consuming (ms): 53
Result: 50 million, time consuming (ms): 55
Result: 50 million, time consuming (ms): 51
Result: 50 million, time consuming (ms): 51
Count based on longaccumulator counter
Result: 50 million, time consuming (ms): 55
Result: 50 million, time consuming (ms): 51
Result: 50 million, time consuming (ms): 52
Result: 50 million, time consuming (ms): 51
Result: 50 million, time consuming (ms): 52
Result: 50 million, time consuming (ms): 52
Result: 50 million, time consuming (ms): 51
Result: 50 million, time consuming (ms): 51
Result: 50 million, time consuming (ms): 51
Result: 50 million, time consuming (ms): 50