๐Ÿ—“๏ธ 29042026 1600
๐Ÿ“Ž #java #concurrency

VOLATILE SYNCHRONIZED ATOMIC DIFFERENCE

The three Java tools for shared-state coordination. They solve overlapping but distinct problems โ€” visibility, atomicity, mutual exclusion โ€” and confusing them produces real bugs (racy counters, lost updates, mysterious infinite loops). The right tool depends on whether you need just visibility, just atomicity, or full mutual exclusion.

What Each Providesโ€‹

ABSTRACT

volatile โ€” visibility + ordering only. Reads see latest writes; reorderings around the variable are restricted. No atomicity, no mutual exclusion.

synchronized / Lock โ€” mutual exclusion + visibility + ordering. Only one thread inside the critical section. Establishes happens-before across enter/exit pairs.

AtomicXxx (java.util.concurrent.atomic) โ€” atomicity + visibility for single compound operations on a single value, via cas_compare_and_swap. Lock-free.

When Each Fitsโ€‹

NeedTool
One thread writes, others read a flagvolatile boolean
Atomic increment of a counterAtomicInteger.incrementAndGet()
Atomic update of a single object referenceAtomicReference.compareAndSet()
Critical section involving multiple fieldssynchronized or ReentrantLock
Read-mostly data, occasional updatevolatile reference + COW pattern
Counter you increment from many threadsLongAdder (better than AtomicLong under contention)

volatile โ€” Visibilityโ€‹

A volatile read sees the most recent write from any thread. Without volatile, the JIT may hoist a read out of a loop, never seeing the updated value.

volatile boolean stop = false;

// Thread A:
while (!stop) {
doWork();
}

// Thread B:
stop = true; // visible to thread A's loop

Without volatile, the JIT can transform while (!stop) into if (!stop) while (true) โ€” a permanent infinite loop. (This is real, not theoretical.)

What volatile does NOT do:

volatile int counter = 0;
counter++; // STILL RACY โ€” read-modify-write, three separate ops

++ is temp = counter; temp = temp + 1; counter = temp;. Two threads reading 5 both write 6. Lost update. volatile ensures visibility, not atomicity.

synchronized โ€” Mutual Exclusionโ€‹

Acquires a monitor (every Java object has one) before entering the block; releases on exit.

synchronized(this) {
// critical section: only one thread at a time
count++;
list.add(x);
}

Properties:

  • Mutual exclusion โ€” guaranteed by the monitor.
  • Happens-before โ€” unlock happens-before subsequent lock (see java_memory_model_happens_before).
  • Reentrant โ€” same thread can re-enter without deadlocking itself.
  • Visibility โ€” every read inside sees writes from prior holders.

Cost: lock acquisition + release. Modern JVMs have biased locking (now removed in JDK 15+) and lightweight locks that make uncontended synchronized near-free; under contention it escalates to a "fat" lock with kernel-level wait/wake.

AtomicXxx โ€” Lock-Free Atomicityโ€‹

AtomicInteger, AtomicLong, AtomicReference, etc. wrap a single value and expose atomic operations:

AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet(); // atomic +1
counter.compareAndSet(5, 10); // CAS: only succeeds if current==5
counter.updateAndGet(v -> v * 2); // atomic functional update

Built on cas_compare_and_swap. Each operation is one read-modify-write, atomic at the CPU level via LOCK CMPXCHG (x86) or LL/SC (ARM).

Properties:

  • No locks โ€” no thread blocks. Failed CAS retries.
  • Visibility โ€” same guarantees as volatile (AtomicXxx fields are internally volatile).
  • Atomicity for single values only โ€” atomically updating two fields together requires either a lock or a wrapper object.

LongAdder โ€” When Contention Is Highโ€‹

AtomicLong under heavy contention has a known limit: every CAS contends on the same cache line. At very high write rates, throughput plateaus.

LongAdder (JDK 8+) sidesteps this with striped counters โ€” multiple internal cells, each thread CAS-es a different cell. sum() reads all cells.

MetricAtomicLongLongAdder
Update under low contentionSlightly fasterSlightly slower (more memory)
Update under high contentionSlow (CAS retries)Much faster
ReadOne wordSums N cells (slower)

Use LongAdder for hot counters (request totals, metrics). Use AtomicLong when you need to read the current value frequently.

Combiningโ€‹

Real concurrent code mixes these:

  • A volatile flag + a synchronized block: flag for fast cancellation check, lock for the critical section once entered.
  • A ConcurrentHashMap (uses CAS + synchronized internally) holding an AtomicInteger per key.
  • An AtomicReference<ImmutableState> with copy-on-write: atomic reference swap, immutable contents.

Performance Hierarchy (Rough)โ€‹

Uncontended โ†’ contended:

ToolUncontendedLightly contendedHeavily contended
volatile read/write~1 ns~1 ns~1 ns
AtomicInteger.incrementAndGet~5 ns~10 nstens of ns (CAS retry)
LongAdder.increment~5 ns~5 ns~5 ns (striped)
synchronized (biased / thin lock)~5 ns~50 ns~ยตs (kernel wait)
ReentrantLock~20 ns~50 ns~ยตs (kernel wait)

Numbers are illustrative; benchmark for your hardware. The shape โ€” locks degrading to kernel waits under contention โ€” holds universally.

Common Pitfallsโ€‹

  • Using volatile for compound operations โ€” counter++, if (x) ... else x = .... Always racy. Use atomic or lock.
  • Forgetting volatile on the singleton field in DCL (double-checked locking) โ€” under reordering, another thread can see a partially-constructed instance. Always:
    private volatile Singleton instance;
  • Locking on String literals or boxed primitives โ€” interned, shared across the JVM. Unrelated code can deadlock you. Use a dedicated lock object.
  • Locking on this in public classes โ€” anyone can also lock on your instance, deadlocking unrelated code paths. Use a private final lock object.
  • Big synchronized blocks โ€” long critical sections kill throughput. Narrow scope to just the contended state.
  • Holding a lock during I/O โ€” calls out to network/disk while holding a lock; thread pool drains; cascading slowness. Release before I/O.
  • AtomicLong everywhere โ€” under contention, LongAdder is far better for counters. Default to LongAdder if you only add and occasionally sum.
  • Mixing locks for the same shared state โ€” synchronized and a separate ReentrantLock on the same data โ†’ no mutual exclusion between them. Pick one mechanism per state.
  • Boolean autoboxing in volatile references โ€” volatile Boolean flag involves valueOf calls and pointer comparisons. Prefer volatile boolean.

Referencesโ€‹