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.