1. The “Why”

Traditional locking (synchronized) is “heavy.” It involves suspending threads, context switching, and OS overhead. Atomic Operations allow us to perform “Read-Modify-Write” cycles as a single, uninterruptible unit at the hardware level. The volatile keyword ensures that changes made by one thread are immediately visible to others, preventing “stale data” bugs caused by CPU caching.

2. Comparison: Volatile vs. Atomic vs. Synchronized

Feature volatile AtomicInteger / AtomicLong synchronized
Visibility Yes (Guarantees fresh data). Yes (Guarantees fresh data). Yes (Guarantees fresh data).
Atomicity No (Doesn’t fix count++). Yes (Fixes count++). Yes (Fixes count++).
Locking Lock-free. Lock-free (uses CAS). Blocking (uses Locks).
Performance Extremely High. High. Medium/Low.

3. The “Golden” Snippet: Performance Metrics

Imagine a high-frequency trading app or a web server. We need to track the number of requests per second without slowing down the app with heavy locks.

import java.util.concurrent.atomic.AtomicLong;

public class BusinessMetrics {
    // 1. Volatile: Ensures threads always read the latest 'active' status
    private volatile boolean isRunning = true;

    // 2. AtomicLong: Safe increment without using 'synchronized'
    private AtomicLong requestCount = new AtomicLong(0);

    public void processRequest() {
        if (isRunning) {
            // This is ATOMIC. No lock is acquired.
            // It uses a CPU instruction called Compare-And-Swap (CAS).
            requestCount.incrementAndGet(); 
        }
    }

    public long getCount() {
        return requestCount.get();
    }

    public void stopService() {
        isRunning = false; // Immediately visible to all threads
    }
}

Code Explanation:

  1. volatile boolean isRunning: Without volatile, if Thread A sets this to false, Thread B might keep running for a long time because it’s reading the value true from its own CPU local cache. volatile forces it to read from Main Memory.
  2. AtomicLong requestCount: If we used a regular long, the operation count++ would require a lock. AtomicLong uses the CAS (Compare-and-Swap) strategy: it reads the value, calculates the new one, and only updates it if the value in memory hasn’t changed since it last looked.
  3. incrementAndGet(): This is a single method call that replaces the entire read -> modify -> write cycle safely.

Example Output:

Thread-1 processed request. Total: 1
Thread-2 processed request. Total: 2
... (1 million requests later) ...
Final Count: 1000000 
(Guaranteed to be exact, unlike a regular long++)

4. The Gotchas

  • The volatile Trap: A very common interview question: “Is volatile int count; count++; thread-safe?” The answer is NO. volatile only ensures you see the latest value; it doesn’t prevent two threads from seeing the same value and both trying to increment it.
  • CAS Spinning: Atomic classes use “optimistic” locking. If 100 threads try to update the same AtomicLong at the exact same time, some will fail and have to “retry” (spin). In cases of extreme contention, LongAdder is often faster than AtomicLong.
  • Compound Actions: Atomics only make one variable atomic. If you need to update two different variables together as one unit, you must go back to using synchronized.