Question 11 · Section 9

What is the advantage of Atomic classes over synchronized?

Atomic classes and synchronized are two different approaches to thread safety:

Language versions: English Russian Ukrainian

Junior Level

Basic Understanding

Atomic classes and synchronized are two different approaches to thread safety:

Approach Principle Analogy
synchronized Pessimistic: “Contention will happen — I’ll lock immediately” One person enters the room, others wait in line
Atomic Optimistic: “Contention is unlikely — I’ll try fast” Everyone tries to enter at once, whoever succeeds — updated

Simple Example

// synchronized — pessimist
public class SynchronizedCounter {
    private int count = 0;

    public synchronized void increment() {
        count++; // Only one thread at a time, others wait
    }
}

// Atomic — optimist
public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet(); // Fast CAS attempt, if it fails — try again
    }
}

When to use which

Situation Choice Why
Simple counter Atomic Faster (CAS ~5ns vs synchronized ~1000ns — no context switch), no deadlock
Compound operation synchronized Need to lock multiple fields
Flag AtomicBoolean Lighter than synchronized
Updating multiple fields synchronized Atomic only works on one field

Middle Level

Why is Atomic faster?

1. No Context Switch

synchronized (under contention):
  Thread → BLOCKED → OS parks → Context Switch → ~1000-5000 ns

Atomic (CAS):
  Thread → RUNNABLE → CAS attempt → ~5-10 ns (no context switch)

With synchronized, the thread transitions to BLOCKED state, which requires calling the OS kernel and context switching — thousands of CPU cycles.

Atomic uses Spin-waiting: the thread doesn’t sleep, but retries the CAS in a loop. This is faster because it doesn’t transfer control to the operating system (no context switch).

2. Progress Guarantees

Problem synchronized Atomic
Deadlock Possible Impossible (for individual Atomic operations; but business logic based on multiple Atomic can still hang)
Priority Inversion Possible Impossible
Forgotten unlock N/A (automatic) N/A
// synchronized — deadlock possible
synchronized(lockA) {
    synchronized(lockB) { // If another thread did the opposite → deadlock
        // ...
    }
}

// Atomic — deadlock impossible
counter1.incrementAndGet(); // Always completes
counter2.incrementAndGet(); // Always completes

How the CAS Cycle Works

// Simplified incrementAndGet implementation
public final int incrementAndGet() {
    for (;;) {                    // Spin cycle
        int current = get();      // 1. Read volatile value
        int next = current + 1;   // 2. Compute new
        if (compareAndSet(current, next)) { // 3. CAS
            return next;          // Success!
        }
        // CAS failed — someone else updated
        // Go to next iteration (without blocking!)
    }
}

When Atomic is WORSE than synchronized?

1. High Contention

100 threads simultaneously doing incrementAndGet():
  - CAS constantly fails
  - Threads spin in cycles, burning CPU at 100%
  - Almost no progress

In this case synchronized may be more effective:
  - Threads queue up
  - CPU is not wasted on empty cycles

2. Compound Operations

// Atomic won't help atomically update x and y
AtomicInteger x = new AtomicInteger(0);
AtomicInteger y = new AtomicInteger(0);

// NOT safe!
void swap() {
    int temp = x.get();
    x.set(y.get()); // Another thread may see intermediate state
    y.set(temp);
}

// synchronized — safe
synchronized(lock) {
    int temp = x;
    x = y;
    y = temp;
}

3. Long Operations

// BAD: long operation in CAS cycle
atomicRef.updateAndGet(current -> {
    // Long computation — CPU will burn under contention!
    return expensiveTransformation(current);
});

// GOOD: long operation in synchronized
synchronized(lock) {
    ref = expensiveTransformation(ref); // Other threads wait, CPU doesn't burn
}

Senior Level

Under the Hood: Bus Contention

Even when CAS succeeds, it generates traffic on the data bus:

Thread on Core 1 does CAS:
  1. lock cmpxchg [memory], new_value
  2. Lock memory bus
  3. Invalidate cache lines on all other cores
  4. Wait for Ack from all cores
  5. Unlock bus

Many Atomic variables can “clog” the server’s memory bus.

Lock-free vs Wait-free vs Obstruction-free

Term Guarantee Example
Lock-free At least one thread completes in finite time AtomicInteger
Wait-free Every thread completes in finite steps LongAdder.add()
Obstruction-free Thread completes if others don’t interfere Some STM

synchronized — none of the above (blocking):

  • Thread can be blocked indefinitely (deadlock, starvation)

Performance: Break-Even Point

Thread count | Atomic (ns/op) | synchronized (ns/op)
───────────────────────────────────────────────────────
1            | 5              | 10
2            | 10             | 15
4            | 20             | 20
8            | 50             | 25
16           | 200            | 30
32           | 1000+          | 40
64           | Spin!          | 50

Numbers are approximate, depend on CPU/JVM. Measure on your hardware via JMH.

Break-even point: ~4-8 threads for simple operations. After that, synchronized becomes faster.

LongAdder as an Evolution of the Atomic Idea

// Problem: AtomicInteger at 100 threads → CAS contention
AtomicInteger counter = new AtomicInteger(0);

// Solution: LongAdder distributes across cells
LongAdder counter = new LongAdder();
counter.increment(); // Each thread to its own cell
long total = counter.sum(); // Sums all cells

LongAdder internally:

// Base value + cell array
volatile long base;
volatile Cell[] cells; // Each thread hashes to its own cell

// @Contended padding — avoid False Sharing
static final class Cell {
    @Contended
    volatile long value;
}

Diagnostics

Thread Dumps

jstack <pid>
# If synchronized is slow — we see BLOCKED threads
"worker-1" BLOCKED (on object monitor)
  - waiting to lock <0x000...>

# If Atomic is slow — no BLOCKED, threads in RUNNABLE
"worker-1" RUNNABLE
  at AtomicInteger.compareAndSet(...)
  // Thread spinning in CAS cycle

-XX:+PrintAssembly

# synchronized → heavy OS calls
call runtime_monitor_enter

# Atomic → single instruction
lock cmpxchg dword ptr [rax], rcx

Best Practices

  1. Atomic for simple operations under low/medium contention
  2. synchronized for compound operations or under high contention
  3. LongAdder for counters under extreme load
  4. Avoid Atomic for long computations — CAS cycle will burn CPU
  5. Monitor: no BLOCKED in dump, but system is slow → look for hot Atomic
  6. Back-off: Thread.onSpinWait() as a CPU hint on CAS retry

Interview Cheat Sheet

Must know:

  • Atomic = optimistic approach (try fast, retry on conflict), synchronized = pessimistic (lock immediately)
  • Atomic uses CAS cycle (spin-waiting): thread doesn’t sleep, retries — no context switch
  • synchronized transitions thread to BLOCKED (~1000-5000 ns), Atomic stays in RUNNABLE (~5-10 ns)
  • Break-even point: ~4-8 threads for simple operations — after that synchronized may become faster
  • Atomic guarantees lock-free progress (at least one thread completes), synchronized can deadlock
  • Atomic does NOT help with compound operations (updating 2+ fields) — synchronized is needed there
  • Under high contention, CAS constantly fails, threads burn CPU at 100% — LongAdder or synchronized is better
  • LongAdder evolves the Atomic idea: distributes counting across cells (per-thread), sums on read

Frequent follow-up questions:

  • Why is Atomic faster under low contention? — No context switch or OS call, one lock cmpxchg instruction
  • What is lock-free vs wait-free? — Lock-free: at least one thread completes; wait-free: every thread completes in finite steps (LongAdder.add)
  • What happens at the CPU level during CAS? — Memory bus lock → invalidate cache lines on all cores → wait for Ack → unlock
  • When is synchronized better than Atomic? — High contention (8+ threads), compound operations, long computations

Red flags (do NOT say):

  • “Atomic is always faster than synchronized” — no, break-even point is ~4-8 threads
  • “Atomic uses locks” — no, it’s a lock-free CAS cycle
  • “Deadlock is impossible in any code with Atomic” — individual Atomic operations are safe, but business logic across multiple Atomic can still hang

Related topics:

  • [[10. How do AtomicInteger, AtomicLong work]]
  • [[12. What is Thread Pool]]
  • [[18. What is deadlock]]