Compare the performance of AtomicLong and LongAdder classes
LongAdder
class is added in Java 8. Official Java docs say: Under high contention, expected throughput of this class compared to AtomicLong is significantly higher, at the expense of higher space consumption.
Let’s check that. I will create a few threads, and the task for every thread will be to increase the number 10000000 times. For benchmarking purposes, I will use Java Microbenchmark Harness. That is a toolkit that helps you implement Java microbenchmarks correctly. You can find my tutorial on the JMH here.
Let’s get back to the comparison of our two classes:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class SimpleBenchmark {
@Benchmark
public long atomicLong() {
AtomicLong atomicLong = new AtomicLong(0);
//create threads that will increment atomicLong 10 milion times, execute those threads and measure result
List<Thread> threads = IntStream.range(0, 10).mapToObj(i -> new Thread(() -> {
IntStream.range(0, 10000000).forEach(i1 -> atomicLong.incrementAndGet());
})).collect(Collectors.toList());
threads.forEach(Thread::start);
threads.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
log.error(e.getMessage());
}
});
return atomicLong.get();
}
@Benchmark
public long longAdder() {
LongAdder longAdder = new LongAdder();
List<Thread> threads2 = IntStream.range(0, 10).mapToObj(i -> new Thread(() -> {
IntStream.range(0, 10000000).forEach(i1 -> longAdder.increment());
})).collect(Collectors.toList());
threads2.forEach(Thread::start);
threads2.forEach(thread -> {
try {
thread.join();
} catch (InterruptedException e) {
log.error(e.getMessage());
}
});
return longAdder.longValue();
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
We compare the code inside the method longAdder
and atomicLong
. Jmh
performs warming up of the JDK and five iterations of the longAdder
and atomicLong
methods. Here is the result when I run this benchmark:
Result "com.chess.SimpleBenchmark.atomicLong":
2054.062 (99.9%) 80.334 ms/op [Average]
(min, avg, max) = (1827.517, 2054.062, 2183.216), stdev = 107.243
CI (99.9%): [1973.728, 2134.396] (assumes normal distribution)
Result "com.chess.SimpleBenchmark.longAdder":
296.987 (99.9%) 31.222 ms/op [Average]
(min, avg, max) = (273.579, 296.987, 453.291), stdev = 41.681
CI (99.9%): [265.765, 328.209] (assumes normal distribution)
As you can see, the average execution time of the longAdder
method is 297ms and, for the atomicLong
is 2054ms.
Further reading
If you like tutorials regarding performance optimization check my other tutorials on the topic here.