⚔️ Atomicity: Your Final Defense Against Race Conditions

Welcome to Pillar #3 of the Concurrency & Multithreading: The Ultimate Engineer’s Bible series.

🔗 ← Previous: Visibility • 🔗 → Next: Coordination
🔝
Parent Blog: The Ultimate Concurrency & Multithreading Guide

“Two threads walk into a bar. One increments a counter. The other resets it. The bar burns down. Nobody knows why.”
— Ancient concurrency proverb.

Visibility isn’t enough. You can see the data — great. But what if two threads update it at the same time?

That’s the real war: race conditions.

Atomicity means:

An operation is indivisible. It either happens fully or not at all.

No other thread can peek in while it’s halfway done. You need atomicity to ensure correctness.

🧠 The Classic Problem: count++

Let’s break down what seems like a harmless line:

count++;

This is not atomic. It expands to:

  1. Read count
  2. Add 1
  3. Write back

Now imagine two threads doing this simultaneously.

They both read count = 5, both compute 6, both write 6.

Expected: 7

Reality: 6

Boom. Race condition.

🛡️ Solution 1: Lock Everything

synchronized(lock) {
count++;
}

Sure, this works. But you’re back to blocking, locking, and slowing everything down.

Can we do better?

⚙️ Solution 2: Use Atomic Classes

Java gives you lock-free, thread-safe primitives with built-in atomicity.

Meet your new weapons:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicReference

Example

AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // Atomic. Thread-safe.

You get visibility, atomicity, and performance. No explicit locks.

🧬 Behind the Scenes: CAS (Compare-And-Swap)

At the core of all this is a CPU-level operation called CAS.

It works like this:

“Hey memory, if the value is still 5, change it to 6.

If someone already changed it, I’ll retry.”

It’s a loop that keeps retrying until it succeeds.

This is what powers compareAndSet():

AtomicInteger count = new AtomicInteger(5);
boolean updated = count.compareAndSet(5, 6);

If the value is still 5, it gets updated to 6. If not, the thread can retry.

No locks. No blocking. Just raw CPU-backed atomic ops.

🧘 Analogy: The Post-It Note on a Fridge

Think of a shared fridge with a post-it note saying “Milk left: 1 bottle.”

Thread A reads the note and goes to add 1 bottle.

Thread B reads the same note and also adds 1 bottle.

They both update the note to “2”. But there’s actually 3 now.

With CAS:

Thread A says:

“If it still says 1, change it to 2.” ✅ Success

Thread B says:

“If it still says 1, change it to 2.” ❌ Fail — someone changed it

Thread B retries:

“Now it’s 2 — change it to 3.” ✅ Success

Everyone wins. That’s atomicity with CAS.

🧪 Useful Atomic Methods

These are gold in any multithreaded context:

  • get() — Fetch the current value
  • set(x) — Overwrite value
  • incrementAndGet() — Atomically add 1 and return
  • compareAndSet(expected, update) — CAS magic

These don’t need synchronized, and they’re extremely performant under high contention.

⚙️ LongAdder and DoubleAccumulator

When you have massive thread counts, even AtomicInteger can become a bottleneck due to CAS retry loops.

Use LongAdder or DoubleAccumulator:

LongAdder adder = new LongAdder();
adder.increment(); // Very fast under high concurrency

Instead of one counter, they use multiple cells — reducing contention.

You only get the final value when you call sum().

🧨 sun.misc.Unsafe — The Forbidden Knowledge

Deep inside Java, there’s a class called Unsafe. It lets you:

  • Allocate memory manually
  • Manipulate memory addresses
  • Perform CAS directly

It’s what backs all the atomic classes.

You should never use it directly unless you’re building concurrency primitives. But knowing it exists? That’s next-level awareness.

👣 Common Pitfalls

  • Compound operations (if (x == 5) x++) are still dangerous — use compareAndSet.
  • Don’t mix Atomic* types with normal int — you lose the atomicity.
  • CAS can fail repeatedly under heavy contention — beware the spin loop.

🔥 Recap: What You Must Remember

  • count++ is not atomic
  • synchronized works, but blocks
  • Atomic* classes give you lock-free atomicity
  • compareAndSet() is the key to concurrency without locks
  • Use LongAdder for high-performance counting
  • Know that Unsafe exists — but don’t use it lightly

🛤️ What’s Next?

You’ve mastered atomicity — the backbone of any correct concurrent system.

🔜 Pillar #4: Coordination — where we make threads work with each other, not against.

🧭 Series Navigation


⚔️ Atomicity: Your Final Defense Against Race Conditions was originally published in Javarevisited on Medium, where people are continuing the conversation by highlighting and responding to this story.

This post first appeared on Read More