This allows multiple threads to safely modify shared data without blocking one another. The CAS operation works using three values:
- The current value stored in memory
- The expected value
- The new value to be written
The operation proceeds as follows:
if (currentValue == expectedValue)
currentValue = newValue;
else
operation fails
The important point is that this comparison and update happen as a single atomic hardware operation. No other thread can modify the value in between the comparison and the update.
For example, suppose the current value of a counter is
10, and two threads attempt to increment it simultaneously.
Current Value = 10
Thread A:
Expected = 10
New Value = 11
Thread B:
Expected = 10
New Value = 11
If Thread A performs the CAS operation first, the comparison succeeds because the current value is still 10. The value is updated to 11.
When Thread B attempts the same CAS operation, it finds that the current value is no longer
10. The comparison fails, so the update is rejected. Thread B simply reads the latest value and retries the operation.
This retry mechanism allows multiple threads to update shared variables safely without using traditional locks.
One reason CAS is extremely efficient is that it is supported directly by modern processors. CPUs provide special atomic instructions, such as CMPXCHG on x86 architectures, that perform the compare-and-update operation without allowing other processors or threads to interfere.
The JVM exposes these hardware instructions through low-level APIs, and the atomic classes internally use them to implement thread-safe operations.
Because CAS is performed by the processor itself, no operating system lock or monitor lock is required, making atomic operations much faster than traditional synchronization for simple updates.
AtomicInteger
The most commonly used atomic class isAtomicInteger. It provides thread-safe operations on an integer value without requiring synchronization.
import java.util.concurrent.atomic.AtomicInteger;
AtomicInteger counter = new AtomicInteger(0);
Instead of writing:
count++;
you write:
counter.incrementAndGet();
Internally, incrementAndGet() repeatedly performs CAS until the update succeeds.
import java.util.concurrent.atomic.AtomicInteger;
class VisitorCounter {
private final AtomicInteger counter = new AtomicInteger();
public void visit() {
counter.incrementAndGet();
}
public int getCount() {
return counter.get();
}
}
Even if hundreds of threads invoke visit() simultaneously, every increment is performed safely without explicit locking.
Useful AtomicInteger Methods
AtomicInteger counter = new AtomicInteger(10);
counter.incrementAndGet(); // 11
counter.decrementAndGet(); // 10
counter.addAndGet(5); // 15
counter.getAndIncrement(); // Returns old value
counter.compareAndSet(15, 20); // CAS operation
counter.get(); // Current value
counter.set(100);
These operations are all atomic and thread-safe.
Other Implementations
AtomicLong
AtomicLong works exactly like AtomicInteger, but stores a long value.
import java.util.concurrent.atomic.AtomicLong;
AtomicLong totalBytes = new AtomicLong(0);
totalBytes.addAndGet(1024);
AtomicBoolean
Sometimes applications need to atomically update a boolean flag.AtomicBoolean initialized = new AtomicBoolean(false);
if (initialized.compareAndSet(false, true)) {
System.out.println("Initialized once");
}
Only one thread can successfully change the value from false to true. All other threads immediately fail the CAS operation.
This pattern is frequently used for one-time initialization.
AtomicReference
Sometimes the shared data is not a primitive value but an object reference.AtomicReference provides CAS operations on object references.
import java.util.concurrent.atomic.AtomicReference;
AtomicReference name = new AtomicReference<>("Java");
Updating the reference:
name.compareAndSet("Java", "Spring");
The update succeeds only if the current reference still points to "Java".
This is widely used in immutable object designs and lock-free data structures.
Atomic Arrays
Java also provides atomic versions of arrays. Examples include:- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
AtomicIntegerArray scores = new AtomicIntegerArray(5);
scores.incrementAndGet(0);
System.out.println(scores.get(0));
Each element in the array can be updated atomically without locking the entire array.
Lock-Free Programming
Traditional synchronization mechanisms such assynchronized and ReentrantLock rely on locks to ensure that only one thread modifies shared data at a time.
While this guarantees correctness, it also introduces thread blocking, context switching, and lock contention. Under heavy concurrency, these factors can significantly reduce application throughput.
Lock-free programming takes a different approach. Instead of preventing multiple threads from accessing shared data simultaneously, it allows them to proceed concurrently and relies on atomic operations such as Compare-And-Swap (CAS) to safely resolve conflicts.
If two threads attempt to update the same variable at the same time, the thread whose CAS operation fails simply retries using the latest value rather than blocking and waiting for a lock.
Because threads are not forced to wait for one another, lock-free algorithms often provide higher scalability and better CPU utilization, especially on multicore processors. Many of Java's atomic classes are implemented using lock-free techniques.
The ABA Problem
One limitation of CAS-based algorithms is the ABA Problem. Suppose a thread reads the valueA from a shared variable and is about to perform a CAS operation.
Before the CAS executes, another thread changes the value:
A → B → A
Now the first thread performs its CAS operation and sees that the value is still A. Since the expected value matches, the CAS succeeds.
However, the value was actually modified twice in the meantime. Although it eventually returned to
A, the intermediate updates may have changed the application's state. The CAS operation cannot detect this because it compares only the current value, not its history.
This situation is known as the ABA Problem.
Java provides classes such as
AtomicStampedReference and AtomicMarkableReference to solve this issue by associating additional metadata with the value.
For example,
AtomicStampedReference maintains a version number (stamp). Even if the value changes from A to B and back to A, the version number changes, allowing the CAS operation to detect that an update occurred.
import java.util.concurrent.atomic.AtomicStampedReference;
AtomicStampedReference reference = new AtomicStampedReference<>("A", 0);
// Read current value and stamp
int[] stampHolder = new int[1];
String value = reference.get(stampHolder);
System.out.println(value); // A
System.out.println(stampHolder[0]); // 0
// CAS succeeds: A -> B, stamp 0 -> 1
reference.compareAndSet("A", "B", 0, 1);
// CAS succeeds: B -> A, stamp 1 -> 2
reference.compareAndSet("B", "A", 1, 2);
// Value is A again, but the stamp is now 2,
// so another thread can detect that updates occurred.
Atomic variables generally outperform traditional locks when operations involve simple updates to a single variable. Since they avoid thread blocking and context switching, they can achieve significantly higher throughput under moderate contention.
However, atomic variables are not always faster. When contention becomes extremely high, many CAS operations may fail simultaneously, causing threads to repeatedly retry their updates.
Excessive retries can waste CPU cycles and reduce performance. In such situations, traditional locking mechanisms may actually perform better because waiting threads are blocked rather than continuously retrying.
Therefore, atomic variables are best suited for simple, independent operations such as counters, flags, sequence generators, and reference updates. Complex business operations involving multiple shared variables are generally better protected using locks or synchronization.
Atomic Variables vs volatile
Although bothvolatile variables and atomic classes provide visibility guarantees, they serve different purposes.
A
volatile variable guarantees that every thread sees the latest value immediately, but it does not make compound operations such as count++ atomic. If multiple threads perform read-modify-write operations concurrently, updates can still be lost.
Atomic variables provide both visibility and atomicity for their supported operations.
Methods such as
incrementAndGet(), addAndGet(), and compareAndSet() execute atomically using CAS, making them suitable for concurrent updates without explicit synchronization.
As a general guideline, use
volatile for simple state flags and visibility requirements, and use atomic classes whenever shared variables require atomic updates.
Atomic Variables vs synchronized
Thesynchronizedkeyword protects an entire critical section, allowing multiple related operations to execute atomically. It is well suited for complex business logic involving several shared variables or multiple dependent operations.
Atomic variables, on the other hand, are optimized for simple operations on a single variable. They avoid lock contention and context switching, often resulting in better performance for counters, sequence numbers, state flags, and similar scenarios.
If an operation requires coordinating multiple variables or maintaining consistency across several objects, atomic variables alone are usually insufficient. In such cases,synchronizedor explicit locks remain the appropriate solution.
Conclusion
Atomic variables provide an efficient alternative to traditional locking for simple concurrent operations. By leveraging the hardware-supported Compare-And-Swap (CAS) instruction, they enable lock-free programming that minimizes thread blocking while maintaining correctness.While atomic classes offer exceptional performance for simple updates, locks remain indispensable for coordinating complex shared state.