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.
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.
The Real Problem
Imagine 100 threads all incrementing the same request counter:
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
Correct, simple, and exact — but under heavy write contention, many threads compete to update the same variable.
LongAdder
Multiple Internal Cells
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:
read current value
calculate new value
try to update only if value has not changedIf 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.
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.
| Question | AtomicInteger | LongAdder |
|---|---|---|
| Data model | One shared integer | Multiple internal cells plus sum |
| Best use case | Exact atomic value | High-throughput statistics |
| High contention | Can become a hotspot | Scales better by spreading updates |
| Good for sequence numbers? | Yes | No |
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.
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.
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:
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:
// 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
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.