Atomic Variables in Java
Atomic variables, in java.util.concurrent.atomic, are classes that hold a single value and update it atomically, as one indivisible step, without locks. AtomicInteger.incrementAndGet() does the whole "read, add one, write" safely even when many threads call it at once, which a plain count++ can't. The main classes are AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference and the high-throughput LongAdder. In this lesson, you will learn their methods and when to use each.
The Main Atomic Classes
| Class | Holds |
|---|---|
AtomicInteger, AtomicLong | An int or long |
AtomicBoolean | A boolean |
AtomicReference<V> | A reference to an object |
AtomicIntegerArray, AtomicLongArray | Arrays whose elements update atomically |
LongAdder, DoubleAdder | Sums that many threads add to at once |
Methods of AtomicInteger
| Method | Description |
|---|---|
get() / set(v) | It is used to read or write the value. |
incrementAndGet() / getAndIncrement() | It adds one and returns the new or the old value. |
addAndGet(delta) | It adds delta and returns the new value. |
compareAndSet(expected, newValue) | It sets the value only if it currently equals expected. |
updateAndGet(fn) | It applies a function atomically and returns the result. |
accumulateAndGet(x, fn) | It combines the value with x atomically. |
Example: Counters and Updates
Let's see an example where four threads update shared atomic values.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;
public class Atomics {
public static void main(String[] args) {
AtomicInteger visits = new AtomicInteger();
AtomicInteger highest = new AtomicInteger();
LongAdder clicks = new LongAdder();
try (ExecutorService pool = Executors.newFixedThreadPool(4)) {
for (int t = 0; t < 4; t++) {
int base = t * 10;
pool.submit(() -> {
for (int i = 0; i < 25_000; i++) {
visits.incrementAndGet();
clicks.increment();
}
highest.accumulateAndGet(base + 7, Math::max);
});
}
}
System.out.println("Visits: " + visits.get());
System.out.println("Clicks: " + clicks.sum());
System.out.println("Highest score: " + highest.get());
AtomicReference<String> leader = new AtomicReference<>("Navika");
boolean changed = leader.compareAndSet("Navika", "Srihitha");
boolean changedAgain = leader.compareAndSet("Navika", "Advitha");
System.out.println("Leader: " + leader.get()
+ " (changes: " + changed + ", " + changedAgain + ")");
}
}Visits: 100000
Clicks: 100000
Highest score: 37
Leader: Srihitha (changes: true, false)Explanation: Four threads each add 25,000 to visits and clicks, and no update is lost: both reach 100,000. accumulateAndGet(x, Math::max) safely keeps the largest value any thread offered (the threads offered 7, 17, 27 and 37). compareAndSet("Navika", "Srihitha") succeeds because the leader really was Navika; the second call fails because the leader is no longer Navika, so Advitha's change is refused.
AtomicLong vs LongAdder
AtomicLong | LongAdder | |
|---|---|---|
| Storage | One value | Several cells, added up on sum() |
| Under heavy contention | Threads retry often, slower | Threads update different cells, much faster |
| Exact current value | get() is exact | sum() is exact only when no updates are happening |
compareAndSet | Yes | No |
| Best for | IDs, sequence numbers, values you compare | Statistics, counters many threads increment |
Common Mistakes
Calling get() then set(get() + 1). That's two steps again; use incrementAndGet().
Using LongAdder where you need compareAndSet or an exact snapshot. Use AtomicLong.
Expecting atomics to coordinate several variables. Each atomic covers only itself.
Interview Corner
What are atomic variables, and how do they work?
Atomic classes such as AtomicInteger, AtomicLong and AtomicReference hold a value and offer lock-free, thread-safe operations like incrementAndGet, compareAndSet and updateAndGet. They're built on the CPU's compare-and-swap instruction: an update succeeds only if the value hasn't changed since it was read, otherwise it retries. They also have volatile semantics, so changes are visible to all threads. For heavily contended counters, LongAdder spreads updates across cells for better throughput.
Practice
- Easy: Replace a synchronized ID generator with an
AtomicLongand generate ten IDs from three threads. - Harder: Keep a "best score and its player" pair in an
AtomicReferenceof a record, and update it from several threads withupdateAndGet.
Key Takeaways
Was this tutorial helpful?