Back to Atomic Classes & CAS
Q2Question 2 of 4

What is LongAdder?

Key Concepts
  • LongAdder (Java 8) reduces CAS contention by striping: instead of all threads CAS-ing one variable, each thread increments its own cell in a padded array.
  • Under low contention (1-2 threads), LongAdder falls back to a single base variable, performing identically to AtomicLong.
  • Under high contention (16+ threads), LongAdder is 5-10x faster than AtomicLong because each thread CAS-es its own cell with minimal contention.
  • sum() returns base plus all cells but is NOT an atomic snapshot: concurrent increments may produce a slightly stale sum.
  • Each Cell is annotated with @Contended to prevent false sharing between cells on adjacent cache lines.
  • Use LongAdder for high-write counters and metrics where exact real-time value is not needed; use AtomicLong when get() must return the exact current value.
  • LongAccumulator generalizes LongAdder: new LongAccumulator(Math::max, 0) tracks the maximum across threads using the same striping technique.

Definition

LongAdder distributes increments across per-thread striped cells to eliminate CAS contention, achieving 5-10x higher throughput than AtomicLong under high contention, at the cost of a non-atomic sum().
  • Striping – instead of all threads competing to CAS a single shared counter (AtomicLong), LongAdder maintains an array of Cell objects. Each thread hashes to its own cell and CAS-es locally.
  • Adaptive – under low contention, LongAdder uses a single base variable (just like AtomicLong). When a CAS on base fails (contention detected), the thread is assigned a Cell and increments that instead.
  • @Contended cells – each Cell is annotated with @Contended, placing it on its own cache line (128-byte padding) to prevent false sharing.
  • Non-atomic sum() – sum() reads base + cells[0].value + cells[1].value + ... without holding any lock. Concurrent increments during the sum may be missed, producing a slightly stale result.
  • LongAccumulator – generalizes LongAdder with a custom accumulator function. new LongAccumulator(Math::max, 0) tracks the maximum value seen across all threads, using the same striping technique.

AtomicLong is like one shared click counter at the entrance to National Bank of India. When 100 customers arrive simultaneously, they all fight over the same button (CAS retry). LongAdder is like 10 separate counters at 10 different doors. Each customer clicks the nearest counter with no fighting. At the end of the day, the manager walks around and adds up all 10 counters. No fighting means 10x faster. But while the manager is adding, someone might click another counter, so the sum is slightly off, which is fine for daily totals but not for real-time account balances.

Understanding the Concepts

What is LongAdder?

LongAdder is a high-throughput counter introduced in Java 8 that reduces CAS contention by distributing increments across an array of striped cells. Instead of all threads competing to update a single shared counter, each thread updates its own cell, and sum() adds them all up.

JAVA
package com.bank.concurrency;

import java.util.concurrent.atomic.LongAdder;

public class LongAdderDemo {
    private static final LongAdder requestCounter = new LongAdder();

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 1_000_000; i++) {
                requestCounter.increment();  // each thread increments its own cell
            }
        };
        Thread[] threads = new Thread[16];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(task, "Worker-" + i);
            threads[i].start();
        }
        for (Thread t : threads) t.join();
        System.out.println("Total requests: " + requestCounter.sum());
        // Always prints: 16000000
    }
}

How does LongAdder work internally?

Under low contention, all increments go to a single base variable using CAS, identical to AtomicLong. When a CAS on base fails (indicating contention), the failing thread is assigned a Cell in the cells[] array based on its thread hash. Subsequent increments by that thread go directly to its Cell with minimal contention.

  • Low contention (1-2 threads): CAS on base succeeds on first try. Performance equals AtomicLong.
  • High contention (16+ threads): CAS on base fails frequently. Threads spread across cells. Each cell has minimal contention.
  • sum() walks base + all cells: base + cells[0].value + cells[1].value + ...

How is LongAdder different from AtomicLong?

| Feature | AtomicLong | LongAdder |
|---|---|---|
| Internal structure | Single volatile long | base + Cell[] array |
| get()/sum() | Exact current value | Eventually consistent sum |
| Low contention | Fast (single CAS) | Same (uses base only) |
| High contention | Slow (CAS retries) | 5-10x faster (striped cells) |
| Memory overhead | 16 bytes | Variable (cells grow) |
| incrementAndGet() | Returns exact new value | Not available (no exact get) |
| Use case | Exact real-time value | High-write counters/metrics |

JAVA
package com.bank.concurrency;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

public class ComparisonDemo {
    // For account balance: exact value needed
    private static final AtomicLong accountBalance = new AtomicLong(100000);

    // For request counter: exact real-time value NOT needed
    private static final LongAdder requestCount = new LongAdder();

    public static void main(String[] args) {
        // AtomicLong: exact value after operation
        long newBalance = accountBalance.addAndGet(-5000);
        System.out.println("Exact balance: " + newBalance);

        // LongAdder: fast increment, sum when needed
        requestCount.increment();
        requestCount.increment();
        long total = requestCount.sum();  // may not include in-flight increments
        System.out.println("Approximate count: " + total);
    }
}

What is LongAccumulator?

LongAccumulator generalizes LongAdder with a custom accumulator function. Instead of summing, you can track maximums, minimums, or any associative, commutative function.

JAVA
package com.bank.concurrency;

import java.util.concurrent.atomic.LongAccumulator;

public class LongAccumulatorDemo {
    private static final LongAccumulator maxTransaction =
            new LongAccumulator(Math::max, 0);

    public static void main(String[] args) {
        maxTransaction.accumulate(2450);
        maxTransaction.accumulate(2500);
        maxTransaction.accumulate(2480);
        System.out.println("Max transaction: " + maxTransaction.get());  // 2500
    }
}

When should you use LongAdder vs AtomicLong?

Use LongAdder when you need high write throughput and only read the total periodically (request counters, metrics dashboards, rate limiters). Use AtomicLong when you need an exact value after each operation (account balances, sequence numbers, resource counts).

What are common interview follow-up questions?

  • Why is sum() not atomic? Because reading base and all cells without a lock allows concurrent modifications during the traversal. Making it atomic would require locking, which defeats the purpose of avoiding contention.
  • Can LongAdder go negative? Yes, decrement() subtracts 1. The semantics are the same as AtomicLong for arithmetic operations.

Visual Overview

DIAGRAM
  LongAdder: Striped Cells
  ═══════════════════════

  AtomicLong (contention):
  Thread-1 ──→ CAS(base) ← fail, retry
  Thread-2 ──→ CAS(base) ← fail, retry
  Thread-3 ──→ CAS(base) ← fail, retry
  All threads fight over ONE variable!

  LongAdder (striped):
  Thread-1 ──→ CAS(cell[0])  ← success
  Thread-2 ──→ CAS(cell[1])  ← success
  Thread-3 ──→ CAS(cell[2])  ← success
  Each thread has its own cell!

  sum() = base + cell[0] + cell[1] + cell[2]

Key Takeaway

LongAdder achieves 5-10x higher throughput than AtomicLong under high contention by distributing increments across per-thread striped cells. Each Cell is @Contended to prevent false sharing. Under low contention, it behaves identically to AtomicLong by using a single base variable. The trade-off is that sum() is not an atomic snapshot: it reads base and all cells without locking, so concurrent increments may produce a slightly stale total.

Use LongAdder for high-write counters, metrics, and rate limiters where the exact real-time value is not critical. Use AtomicLong when you need an exact value after each operation, such as account balances or sequence numbers. LongAccumulator extends the pattern to custom functions like Math::max for tracking maximums across threads.

At National Bank of India, the API gateway request counter using AtomicLong consumed 15% CPU under 200 concurrent threads due to CAS contention. Switching to LongAdder reduced CPU usage to under 1%, with sum() called once per second for the Grafana monitoring dashboard.

Remember one sentence: LongAdder stripes increments across per-thread cells to eliminate CAS contention, making it 5-10x faster than AtomicLong for counters that do not need exact real-time reads.

---

Share
Use ← → arrow keys to navigate