Atomic variables in java.util.concurrent.atomic are designed for lock-free, thread-safe operations on single variables. They leverage low-level CPU instructions like Compare-And-Swap (CAS) to ensure data consistency without the overhead of synchronized blocks.
Here’s a guide on how to use the most common atomic classes.
1. AtomicInteger and AtomicLong
These are used for numeric counters or IDs. Instead of using ++ (which is not atomic), you use methods like incrementAndGet().
package org.kodejava.util.concurrent;
import java.util.concurrent.atomic.AtomicInteger;
public class CounterExample {
private final AtomicInteger counter = new AtomicInteger(0);
public void increment() {
// Atomically increments by one and returns the new value
int newValue = counter.incrementAndGet();
System.out.println("Current Value: " + newValue);
}
public int getValue() {
return counter.get();
}
}
2. AtomicBoolean
Useful for flags that need to be checked and updated across threads, such as a “running” state.
package org.kodejava.util.concurrent;
import java.util.concurrent.atomic.AtomicBoolean;
public class Worker {
private final AtomicBoolean initialized = new AtomicBoolean(false);
public void init() {
// compareAndSet(expectedValue, newValue)
// Only sets to true if it was currently false
if (initialized.compareAndSet(false, true)) {
System.out.println("Performing one-time initialization...");
}
}
}
3. AtomicReference
Used to wrap any object reference. This is great for implementing non-blocking algorithms where you need to update an entire object state atomically.
package org.kodejava.util.concurrent;
import java.util.concurrent.atomic.AtomicReference;
public class StateManager {
private final AtomicReference<String> status = new AtomicReference<>("IDLE");
public void updateStatus(String oldStatus, String newStatus) {
boolean success = status.compareAndSet(oldStatus, newStatus);
if (success) {
System.out.println("Status changed to: " + newStatus);
}
}
}
4. Advanced Accumulators (LongAdder)
If you have a very high-contention environment (many threads constantly updating a sum), LongAdder is generally faster than AtomicLong because it maintains internal cells to reduce contention.
package org.kodejava.util.concurrent;
import java.util.concurrent.atomic.LongAdder;
public class HighContentionCounter {
private final LongAdder adder = new LongAdder();
public void add() {
adder.increment();
}
public long getTotal() {
return adder.sum();
}
}
Key Methods to Remember
get()/set(): Read or write the value (similar tovolatile).lazySet(): Eventually sets the value; faster but doesn’t guarantee immediate visibility to other threads.compareAndSet(expect, update): The heart of atomic variables. Updates only if the current value matchesexpect.getAndAccumulate(delta, accumulatorFunction): (Java 8+) Allows complex atomic updates using a Lambda.
When to use them?
- Use them for simple counters, sequence generators, or flags.
- Avoid them if you need to update multiple dependent variables at once; in that case, a
ReentrantLockorsynchronizedblock is safer to ensure the entire operation is atomic.
