How do AtomicInteger, AtomicLong work?
AtomicInteger and AtomicLong are thread-safe wrappers over the int and long primitives.
Junior Level
Basic Understanding
AtomicInteger and AtomicLong are thread-safe wrappers over the int and long primitives.
Why: when multiple threads simultaneously modify a regular int variable, updates are lost. AtomicInteger solves this by guaranteeing atomicity of each operation without locks. They provide atomic (indivisible) operations without locks via CAS. Atomic = other threads cannot see an intermediate state.
Creation and Basic Operations
// Creation
AtomicInteger atomicInt = new AtomicInteger(0);
AtomicLong atomicLong = new AtomicLong(0L);
// Increment/decrement
atomicInt.incrementAndGet(); // ++i (returns new)
atomicInt.getAndIncrement(); // i++ (returns old)
atomicInt.decrementAndGet(); // --i
atomicInt.getAndDecrement(); // i--
// Arithmetic
atomicInt.addAndGet(10); // i += 10
atomicInt.getAndAdd(10); // returns old, then += 10
// Setting
atomicInt.set(100); // Direct set
atomicInt.getAndSet(200); // Returns old, sets new
// CAS
atomicInt.compareAndSet(200, 300); // If == 200, set to 300
// Getting
int value = atomicInt.get(); // Current value
Example: Request Counter
public class RequestCounter {
private final AtomicInteger count = new AtomicInteger(0);
public void handleRequest(Request req) {
int currentCount = count.incrementAndGet();
System.out.println("Request #" + currentCount);
// Processing...
}
public int getTotalRequests() {
return count.get();
}
}
Why not just int?
// NOT safe:
int count = 0;
count++; // 3 operations: read → modify → write
// Two threads may read the same value and lose an update
// Safe:
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // Atomic operation — CAS at CPU level
Main Methods
| Method | Description | Returns |
|---|---|---|
get() |
Current value | int/long |
set(v) |
Set value | void |
incrementAndGet() |
++i | New value |
getAndIncrement() |
i++ | Old value |
addAndGet(delta) |
i += delta | New value |
getAndAdd(delta) |
i += delta | Old value |
compareAndSet(exp, upd) |
CAS | true/false |
updateAndGet(fn) |
Update via function | New value |
Middle Level
Internal Structure
public class AtomicInteger extends Number implements java.io.Serializable {
// 1. volatile field for visibility
private volatile int value;
// 2. VarHandle for direct memory access (Java 9+)
private static final VarHandle VALUE;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
VALUE = l.findVarHandle(AtomicInteger.class, "value", int.class);
} catch (ReflectiveOperationException e) {
throw new Error(e);
}
}
public AtomicInteger(int initialValue) {
value = initialValue;
}
}
Different Write Methods
AtomicInteger counter = new AtomicInteger(0);
// 1. set(v) — regular volatile write
counter.set(100);
// Immediately visible to all threads, full memory barrier
// 2. lazySet(v) — relaxed write (Java 9+: setOpaque)
counter.lazySet(100);
// Does not guarantee immediate visibility, but faster because it doesn't set a full memory barrier — the processor is not required to immediately synchronize cache lines with other cores.
// Useful for resetting state that will soon be read by the same thread
// 3. compareAndSet(exp, upd) — full CAS
counter.compareAndSet(0, 100);
// If == 0, set to 100. Full barrier.
// 4. updateAndGet(fn) — update via function
counter.updateAndGet(x -> x * 2); // Multiply by 2
| Method | Happens-Before | Speed | When to use |
|---|---|---|---|
set(v) |
Yes | Standard | Regular write |
lazySet(v) |
No (eventual) | Faster | Flag reset |
compareAndSet |
Yes | Standard | Lock-free algorithms |
weakCompareAndSet |
No | Faster | Optimized cycles |
Safety on 32-bit Systems
// On a 32-bit JVM:
long value = 0x0123456789ABCDEFL;
// Written in TWO steps:
// 1. 0x89ABCDEF (lower 32 bits)
// 2. 0x01234567 (upper 32 bits)
// Another thread may see a "mixed" value!
// AtomicLong guarantees atomicity:
AtomicLong atomicLong = new AtomicLong(0x0123456789ABCDEFL);
// Uses CMPXCHG8B instruction or internal locking
updateAndGet — Flexible Atomic Operation
AtomicInteger flags = new AtomicInteger(0);
// Atomically set a bit
flags.updateAndGet(current -> current | 0x01);
// Atomically clear a bit
flags.updateAndGet(current -> current & ~0x01);
// Atomically toggle a bit
flags.updateAndGet(current -> current ^ 0x01);
False Sharing (cache line problem)
When two AtomicInteger instances are next to each other in an array, they may end up in the same 64-byte CPU cache line. If thread A writes to counters[0] and thread B reads counters[1] — they compete for one cache line, even though these are different counters.
Solution: @Contended annotation (Java 8+, requires -XX:-RestrictContended) — adds padding around the field so it occupies a separate cache line.
@sun.misc.Contended
AtomicInteger counter; // 64 bytes of padding before and after
For long[] arrays, false sharing is real:
long[] counters = new long[8];
// counters[0] and counters[1] in the same cache line → two threads compete
When AtomicInteger is a poor choice
- High contention (100+ threads): CAS contention kills performance — use LongAdder
- Compound operations: atomically updating two fields — need synchronized
- Long computations between read and write: CAS will constantly fail — better to use Lock
Senior Level
Under the Hood: VarHandle and Memory Access API
Java 9+ uses VarHandle instead of Unsafe:
// Atomic via VarHandle
public final int getAndAdd(int delta) {
return (int)VALUE.getAndAdd(this, delta);
}
// VarHandle translates to native instructions:
// VALUE.getAndAdd → lock xadd [memory], delta (x86)
getAndAdd at x86 Level
; lock xadd — atomic addition returning old value
; eax = delta, [memory] = counter
lock xadd [memory], eax
; [memory] = [memory] + eax
; eax = old value of [memory]
AtomicIntegerFieldUpdater — Memory Savings
For cases where millions of objects need an atomic int:
// BAD: millions of extra AtomicInteger objects
class User {
AtomicInteger loginAttempts = new AtomicInteger(0); // 24 bytes × 1M = 24MB
}
// GOOD: one field + one static updater
class User {
volatile int loginAttempts = 0; // 4 bytes × 1M = 4MB
private static final AtomicIntegerFieldUpdater<User> UPDATER =
AtomicIntegerFieldUpdater.newUpdater(User.class, "loginAttempts");
public int incrementAttempts() {
return UPDATER.incrementAndGet(this);
}
}
CAS Contention and ID Generator
// BAD: global ID generator at 1000 RPS
AtomicLong idGenerator = new AtomicLong(0);
public long nextId() {
return idGenerator.incrementAndGet(); // High contention!
}
// GOOD: ID range allocation
public class IdRangeGenerator {
private final AtomicLong nextRangeStart = new AtomicLong(0);
private static final long RANGE_SIZE = 1000;
// Issues a batch of IDs — each service/thread gets its own range
public IdRange allocateRange() {
long start = nextRangeStart.getAndAdd(RANGE_SIZE);
return new IdRange(start, start + RANGE_SIZE);
}
}
Performance and Highload
Benchmark (approximate)
| Operation | Time | Note |
|---|---|---|
get() |
~1 ns | Volatile read |
set() |
~5 ns | Volatile write |
incrementAndGet() (no contention) |
~5-10 ns | One CAS |
incrementAndGet() (100 threads) |
~100+ ns | CAS contention |
lazySet() |
~2-3 ns | Without full barrier |
Optimization via Batching
// Local counter + atomic flush
public class BatchedCounter {
private int local = 0;
private final AtomicInteger flushed = new AtomicInteger(0);
public synchronized void increment() {
local++;
if (local >= 100) { // Flush every 100
flushed.addAndGet(local);
local = 0;
}
}
public int getTotal() {
synchronized(this) {
return flushed.get() + local;
}
}
}
Diagnostics
Bytecode Analysis
javap -c -p AtomicInteger.class
public final int getAndAddInt(int);
Code:
0: aload_0
1: invokevirtual get
4: iload_1
5: iadd
6: dup
7: invokevirtual compareAndSet // ← CAS call
Profiling CAS Failures
// Track CAS attempts vs successes
AtomicLong successes = new AtomicLong(0);
AtomicLong attempts = new AtomicLong(0);
for (int i = 0; i < 1_000_000; i++) {
attempts.incrementAndGet();
if (counter.compareAndSet(expected, next)) {
successes.incrementAndGet();
}
}
double successRate = (double) successes.get() / attempts.get();
System.out.println("CAS success rate: " + (successRate * 100) + "%");
// < 50% → high contention
Best Practices
- AtomicInteger for counters — under moderate contention
- AtomicLong for 64-bit counters — safe on 32-bit JVMs
- LongAdder under high contention — distributed cells
- lazySet for flag resets — faster than set
- updateAndGet for complex updates — atomic function
- AtomicIntegerFieldUpdater for memory savings — millions of objects
- ID range allocation — instead of global AtomicLong at 1000+ RPS
Interview Cheat Sheet
Must know:
- AtomicInteger/AtomicLong — thread-safe wrappers over int/long, provide atomicity without locks
- Work via CAS (compare-and-swap) — a hardware CPU instruction (lock cmpxchg on x86)
- Java 9+ use VarHandle instead of Unsafe for memory access
- incrementAndGet() = spin cycle: read → compute → CAS → retry on failure
- lazySet() — relaxed write without full memory barrier, faster for flag resets
- Under high contention (100+ threads), CAS contention kills performance — need LongAdder
- AtomicLong guarantees atomic read/write even on 32-bit JVMs (where long is written in 2 steps)
- AtomicIntegerFieldUpdater saves memory when millions of objects need an atomic field
Frequent follow-up questions:
- How does getAndIncrement() differ from incrementAndGet()? — The first returns the old value (i++), the second — the new (++i)
- Why is CAS better than synchronized? — No context switch, deadlock impossible, ~5ns vs ~1000ns under low contention
- What is false sharing? — Two AtomicInteger in the same CPU cache line compete even at different addresses; solved with @Contended
- When is AtomicInteger a poor choice? — High contention (LongAdder is better), compound operations (need synchronized), long computations between read/write
Red flags (do NOT say):
- “Atomic uses synchronized internally” — no, it’s lock-free CAS
- “AtomicInteger is always faster than synchronized” — no, at 8+ threads synchronized may be faster
- “compareAndSet blocks the thread on failure” — no, it just returns false, the thread spins in a loop
Related topics:
- [[11. What is the advantage of Atomic classes over synchronized]]
- [[12. What is Thread Pool]]
- [[15. What does ExecutorService do]]
- [[18. What is deadlock]]