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:
volatile boolean isRunning: Withoutvolatile, if Thread A sets this tofalse, Thread B might keep running for a long time because it’s reading the valuetruefrom its own CPU local cache.volatileforces it to read from Main Memory.AtomicLong requestCount: If we used a regularlong, the operationcount++would require a lock.AtomicLonguses 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.incrementAndGet(): This is a single method call that replaces the entireread -> modify -> writecycle 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
volatileTrap: A very common interview question: “Isvolatile int count; count++;thread-safe?” The answer is NO.volatileonly 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
AtomicLongat the exact same time, some will fail and have to “retry” (spin). In cases of extreme contention,LongAdderis often faster thanAtomicLong. - 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.