Backend Development 8 min read

Testing Volatile Thread Safety and Comparing LongAdder vs AtomicInteger Performance in Java

This article examines the thread‑safety of the volatile keyword under multi‑write scenarios, demonstrates its failure with a concurrent counter test, and benchmarks LongAdder against AtomicInteger using JMH, revealing LongAdder’s superior performance under high contention and AtomicInteger’s advantage in low‑contention environments.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Testing Volatile Thread Safety and Comparing LongAdder vs AtomicInteger Performance in Java

Alibaba's "Java Development Manual" (latest Songshan edition) notes that volatile can solve visibility problems for a single‑writer‑multiple‑reader scenario but cannot guarantee thread safety when multiple threads write.

The article highlights two key points:

Non‑single‑writer scenarios such as count++ should not use volatile .

For JDK 8 and later, LongAdder is recommended over AtomicLong (or volatile ) because it offers better performance under high contention.

volatile Thread‑Safety Test

A simple program increments a volatile int count 100 000 times in the main thread while a second thread decrements it the same number of times. The expected final value is 0, but the actual result is 1063, confirming that volatile is not thread‑safe in a multi‑write environment.

public class VolatileExample {
    public static volatile int count = 0; // counter
    public static final int size = 100000; // loop count

    public static void main(String[] args) {
        // ++ 100k times in a separate thread
        Thread thread = new Thread(() -> {
            for (int i = 1; i <= size; i++) {
                count++;
            }
        });
        thread.start();
        // -- 100k times in the main thread
        for (int i = 1; i <= size; i++) {
            count--;
        }
        // wait for the other thread to finish
        while (thread.isAlive()) {}
        System.out.println(count); // prints the result
    }
}

The test result (1063) matches the manual’s claim that volatile does not provide atomicity for multiple writers.

LongAdder vs AtomicInteger Benchmark

Using Oracle’s JMH (Java Microbenchmark Harness), the article benchmarks LongAdder against AtomicInteger under two contention levels: 1000 threads (high contention) and 100 threads (low contention). The benchmark code is:

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 1, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 5, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
@Threads(1000)
public class AlibabaAtomicTest {
    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(AlibabaAtomicTest.class.getSimpleName())
                .build();
        new Runner(opt).run();
    }

    @Benchmark
    public int atomicTest(Blackhole blackhole) throws InterruptedException {
        AtomicInteger atomicInteger = new AtomicInteger();
        for (int i = 0; i < 1024; i++) {
            atomicInteger.addAndGet(1);
        }
        return atomicInteger.intValue();
    }

    @Benchmark
    public int longAdderTest(Blackhole blackhole) throws InterruptedException {
        LongAdder longAdder = new LongAdder();
        for (int i = 0; i < 1024; i++) {
            longAdder.add(1);
        }
        return longAdder.intValue();
    }
}

Results show that with 1000 concurrent threads, LongAdder is about 1.53× faster than AtomicInteger . With only 100 threads, AtomicInteger outperforms LongAdder , indicating that LongAdder shines under high contention because it distributes updates across multiple cells, reducing CAS retries, whereas AtomicInteger suffers from frequent CAS contention.

Conclusion

The experiments confirm that volatile is not thread‑safe for multi‑write scenarios, and that the choice between LongAdder and AtomicInteger should depend on the expected contention level: use AtomicInteger for low‑contention workloads and LongAdder for high‑contention, high‑parallelism cases.

JavaperformanceConcurrencyLongAddervolatileJMHAtomicInteger
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.