Java Concurrency + Performance

What is the difference between LongAdder and AtomicInteger?

AtomicInteger updates one shared value atomically. LongAdder spreads updates across multiple internal cells to reduce contention, making it better for high-throughput counters.

ConcurrencyAtomic ClassesPerformance

The Short Answer

AtomicInteger gives you one integer value that can be updated atomically.

LongAdder is designed for high-throughput counters. Instead of forcing every thread to update one exact shared number, it spreads updates across multiple internal counters and adds them up when you ask for the total.

AtomicInteger is usually better when you need an exact value after each operation. LongAdder is usually better when many threads are incrementing a shared statistic, metric, or counter.

The Real Problem

Imagine 100 threads all incrementing the same request counter:

java
counter.incrementAndGet();

With AtomicInteger, every thread is trying to update the same single memory location. The operation is thread-safe, but that one location can become a hotspot.

AtomicInteger

One Shared Counter

value = 42
Thread A
Thread B
Thread C
Everyone fights over one value

Correct, simple, and exact — but under heavy write contention, many threads compete to update the same variable.

LongAdder

Multiple Internal Cells

cell 1 = 10
cell 2 = 12
cell 3 = 9
cell 4 = 11
sum() = 42

Updates are spread out, so threads collide less often. The final total is calculated by adding the cells together.

Why AtomicInteger Can Become a Bottleneck

AtomicInteger commonly uses compare-and-set (CAS) style updates. The idea is:

java
read current value
calculate new value
try to update only if value has not changed

If another thread changed the value first, your update has to retry. With a few threads, this is usually fine. With many threads all hammering the same counter, retries can become expensive.

AtomicInteger solves correctness. LongAdder solves the extra problem of high-contention throughput.

Why LongAdder Is Faster Under Contention

LongAdder reduces the pressure on one shared variable. Instead of one crowded checkout line, it creates multiple checkout lines.

1. Low contention

A thread may update a base value directly.

2. Contention appears

LongAdder creates internal cells.

3. Threads spread out

Different threads update different cells.

This is why LongAdder is a great fit for metrics, request counts, hit counters, and frequency maps.

The Important Tradeoff

LongAdder is not simply “better AtomicInteger.” It makes a tradeoff.

QuestionAtomicIntegerLongAdder
Data modelOne shared integerMultiple internal cells plus sum
Best use caseExact atomic valueHigh-throughput statistics
High contentionCan become a hotspotScales better by spreading updates
Good for sequence numbers?YesNo

The most important point: LongAdder.sum() is not an atomic snapshot while other threads are still updating it. For statistics and monitoring, that is usually fine. For sequence numbers, IDs, or exact coordination, it is not fine.

When AtomicInteger Is the Better Choice

  • You need an exact value immediately after each update.
  • You are generating sequence numbers.
  • You need compare-and-set logic.
  • The value participates in coordination or control flow.
  • Contention is low and simplicity matters.
java
AtomicInteger sequence = new AtomicInteger(0);

int nextId = sequence.incrementAndGet();

When LongAdder Is the Better Choice

  • Many threads increment the same counter.
  • You are collecting metrics or statistics, and don't immediately care about the updated value.
  • You mostly care about the total when reporting.
  • Throughput matters more than a perfectly atomic read.
  • You are building a frequency map with ConcurrentHashMap.
java
ConcurrentHashMap<String, LongAdder> requestCounts =
    new ConcurrentHashMap<>();

public void recordRequest(String endpoint) {
    requestCounts
        .computeIfAbsent(endpoint, key -> new LongAdder())
        .increment();
}

Common Interview Trap

A common mistake is saying:

“LongAdder is always better because it is faster.”

That is not quite right. LongAdder can be faster for highly contended counters, but it is not the right tool when each read must represent one exact, globally ordered value.

For example, do not use LongAdder for generating unique sequence numbers:

java
// Bad idea for unique IDs
LongAdder id = new LongAdder();

id.increment();
long nextId = id.sum();

Multiple threads could increment and read in ways that do not give each thread a clean unique sequence value.

Interview-Friendly Answer

AtomicInteger maintains one atomic integer and is good when I need an exact value or atomic compare-and-set behavior. LongAdder is better for high-contention counters, like metrics or request counts, because it spreads updates across internal cells and sums them later. The tradeoff is that sum() is not an atomic snapshot during concurrent updates, so I would not use it for sequence numbers or coordination.

Final Takeaway

Use AtomicInteger when

You need exact, immediate, single-value atomic behavior.

Use LongAdder when

You need a scalable counter that many threads update frequently.