skip to content

LongAdder & LongAccumulator

LongAdder spreads increments across multiple cells so contending threads stop fighting over one cache line, trading exact intermediate reads for throughput. Interviewers ask when to prefer it over AtomicLong: write-heavy counters read rarely, such as metrics.

part ofJavaoverview, primer and where to startread it →
on this pageshow

questions

4

What is LongAdder, and why might you prefer it over AtomicLong for a heavily-incremented counter?

level: middleimportance: should knowfreq 55%

answer

  1. AtomicLong = one hot location, everyone CASes it
  2. LongAdder = stripe writes across padded cells
  3. sum() = base + all cells, NOT an atomic snapshot
  4. Write-heavy / read-rare → LongAdder
  5. Need compareAndSet or frequent exact reads → AtomicLong

basics

~20 s

LongAdder is a counter for many threads. Instead of one shared number, it keeps several internal cells so threads update different ones and rarely collide. You read the total with sum(). Under heavy concurrent writes it's faster than AtomicLong.

solid answer

~40 s

AtomicLong keeps a single value that every thread updates with a compare-and-swap (CAS) retry loop. Under high write contention, threads keep failing and retrying that CAS on the same memory location, which serializes them and wastes CPU. LongAdder instead spreads writes across an internal array of cells: different threads hash to different cells, so their CAS operations hit different memory and rarely conflict. add() updates one cell; sum() adds all cells (plus a base) for the total. The trade-off: sum() is not a precise instantaneous snapshot if updates run concurrently, and LongAdder uses more memory than a single long. So prefer LongAdder for write-heavy, read-rarely counters (metrics, statistics); prefer AtomicLong when you need a single atomic value you frequently read or compare-and-set, or when contention is low.

code

java · 13 lines
java
import java.util.concurrent.atomic.LongAdder;

LongAdder requests = new LongAdder();

// Hot path: many threads, fire-and-forget
requests.increment();        // add(1) -> base or a cell, lock-free CAS
requests.add(5);

// Cold path: read occasionally
long total = requests.sum(); // base + all cells; NOT an atomic snapshot

// Interval metric: read and zero in one step
long sinceLast = requests.sumThenReset();

go deeper

for a junior

Knows LongAdder is a thread-safe counter and that it's a good choice when many threads increment a counter; reads the total with sum().

for a middle

Explains the single-hot-location problem of AtomicLong under contention and that LongAdder stripes writes across cells; knows sum() is not a perfect snapshot.

for a senior

Articulates the CAS contention / cache-line bouncing mechanism, the base+cells structure, the read-vs-write and memory trade-offs, and gives concrete decision criteria (metrics vs sequence generator).

for a principal

Reasons about when striping helps vs hurts at the system level (false sharing, @Contended padding, NUMA, memory footprint per counter at scale), and whether a metrics library already abstracts this; weighs LongAdder against AtomicLong, sharded counters, or thread-local aggregation.

## The problem LongAdder solves A **counter** is a number many threads increment. The naive `count++` is three steps (read, add one, write), so two threads can interleave and lose an update — a *race condition*. The classic fix is `AtomicLong`. **`AtomicLong`** holds one `long` and updates it with **compare-and-swap (CAS)**: a hardware instruction that says "if this memory still holds the value I read, replace it with my new value; otherwise tell me you failed." `incrementAndGet()` is a loop: read current value `v`, compute `v+1`, CAS from `v` to `v+1`; if CAS fails (another thread changed it first), reread and retry. CAS gives atomicity **without locks** — no thread is ever blocked, it just retries. **Contention** is the issue. When dozens of threads all hammer the *same* `AtomicLong`, they all CAS the *same* memory address. Only one wins per round; the rest fail and retry. The cache line holding that value ping-pongs between CPU cores (**cache-line bouncing** / false-true sharing), and throughput collapses — effectively the threads serialize on one location. ## How LongAdder works `LongAdder` (Java 8+, in `java.util.concurrent.atomic`) trades a single hot location for **many cool ones**. Internally it has: - a `base` field (a plain `long` updated by CAS when there's no contention), and - a lazily-grown array of **`Cell`** objects, each holding its own `long`. `add(x)` first tries to CAS `base`. If that succeeds (low contention), done. If it fails, the thread is routed to a `Cell` chosen by a per-thread hash (a probe value), and it CASes *that* cell. Because different threads hash to different cells, their CAS operations touch **different memory addresses on different cache lines**, so they rarely collide. The array grows (up to roughly the number of CPUs) when contention is still detected, and the `Cell`s are padded (`@Contended`) so no two share a cache line. `increment()` is just `add(1)`. To get the total, **`sum()`** returns `base` plus every cell's value. This is the key trade-off: `sum()` is **not atomic** with respect to concurrent `add`s. It reads the cells one by one, so if updates are happening during the traversal, the result reflects some in-between moment — it's eventually-consistent, not a precise instantaneous snapshot. `sumThenReset()` exists for interval metrics. ## When to use which - **Prefer `LongAdder`** for **write-heavy, read-rare** counters under contention: request counts, hit/miss tallies, throughput metrics — anywhere you fire-and-forget `increment()` from many threads and only occasionally read the total. - **Prefer `AtomicLong`** when you (a) need a single value you read frequently or must read atomically, (b) need `compareAndSet`/`getAndSet` to drive logic (e.g. a sequence generator, a one-shot flag, an ID allocator), or (c) have low contention where the extra memory and indirection of LongAdder aren't justified. ## Cost trade-offs LongAdder uses more memory (an array of padded cells, sized near CPU count) and gives up a cheap exact read. In exchange, under heavy contention it scales far better — write throughput stays roughly flat as threads increase, whereas AtomicLong degrades. The whole idea is *striping* the contended state.

  • Is LongAdder.sum() exact?
    Not under concurrent updates. It sums the base and each cell sequentially, so a concurrent add can land before or after a given cell is read. The result is correct only if it converges in a quiescent moment. For an exact running value you read constantly, AtomicLong is the better fit.
  • Does LongAdder use locks?
    No for the hot path — adds use CAS on the base or a cell. It only takes a short internal lock (a busy spin/CAS on a cellsBusy flag) when it has to allocate or resize the cell array, which is rare.

saying these in an interview costs you the question

  • Claiming sum() returns a precise atomic snapshot during concurrent updates
  • Saying LongAdder is always faster than AtomicLong — at low contention AtomicLong can be as fast and uses less memory
  • Thinking LongAdder supports compareAndSet (it doesn't — no atomic value semantics, only add/sum/reset)
  • Confusing it with a lock-based counter; the fast path is lock-free CAS
  • Assuming LongAdder gives a single canonical value you can CAS on

context

open as a page

Explain the contention problem with a shared AtomicLong and how striping in LongAdder addresses it at the hardware level.

level: seniorimportance: should knowfreq 40%

basics

~20 s

Many threads updating one AtomicLong keep failing their compare-and-swap and retrying because they all touch the same memory. That memory bounces between CPU caches. LongAdder gives each thread a different cell, so they touch different memory and stop fighting.

open as a page

What does LongAccumulator add over LongAdder, and when would you reach for it?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

LongAccumulator is the general version of LongAdder. You give it a function (like max or multiply) and a starting value, and it combines all the values you feed in using that function across multiple cells. LongAdder is just LongAccumulator with addition.

open as a page

You're profiling a service and a shared metric counter shows up as a hotspot under load. Walk through how you'd decide between AtomicLong, LongAdder, and other options.

level: principalimportance: nice to knowfreq 28%

basics

~20 s

First confirm it's really write contention, not something else. If many threads just increment a counter and you read it rarely, switch to LongAdder. If you need to read or compare-and-set a single exact value, keep AtomicLong. Or use a metrics library that already handles it.

open as a page