TL;DR ⚡

Abstract

  • Race condition = outcome depends on thread timing; classic form is check-then-act.
  • volatile = visibility only. synchronized = atomicity (blocks). atomic = lock-free single-variable (CAS).
  • Hook: volatile = see it · synchronized = one-at-a-time · atomic = safe ++ without a lock.

The race: joint account

Balance ₹10,000. Two ₹8,000 withdrawals at once:

void withdraw(int amount) {
    if (balance >= amount) {      // both READ 10,000, both pass  ← check
        balance = balance - amount; // both subtract 8,000          ← act
    }
}

Both read before either writes → both withdraw → −6,000. The read-check-write wasn’t atomic. This is a check-then-act race.

Fix — synchronized: only one thread inside withdraw at a time; the second reads the updated balance and fails the check.

The three tools — different jobs

volatile — VISIBILITY only

Scenario: a “trading halted” / daily interest-rate flag. Without volatile, threads may cache a stale copy in a CPU register. volatile forces reads from main memory, so everyone sees the latest value.

⚠️ volatile does NOT make count++ safe — that’s read-add-write (3 steps). Volatile = one writer, many readers of a simple value.

synchronized — ATOMICITY (+ visibility)

For compound operations (check-then-act, multi-field updates). Correct for anything, but blocks other threads → slowest. synchronized on a method locks the whole object (this).

atomic (CAS) — lock-free single variable

AtomicLong txnCount; txnCount.incrementAndGet(); — atomic ++ with no lock. Uses CAS (compare-and-swap): “if the value is still what I read, swap it; else retry.” No blocking. Fast for a single hot counter. Limit: protects one variable only.

Decision table

ToolSolvesBanking exampleCost
volatilevisibilitytrading-halt / rate flagcheap; no atomicity
synchronizedatomicity of compound opsfull withdraw / transferblocks; slowest
atomic (CAS)atomicity of ONE vartxn-per-second counterfast; single var

Decision line: “volatile for a visibility flag, atomic for a single hot counter, synchronized when the invariant spans multiple fields or a check-then-act.”

Test Yourself

What is a race condition (classic form)?::Outcome depends on thread timing; check-then-act — two threads both pass a check before either commits.
volatile vs synchronized vs atomic?::volatile = visibility only; synchronized = atomicity for compound ops but blocks; atomic = lock-free single-variable via CAS.
Why doesn’t volatile make count++ safe?::++ is read-add-write (3 steps); volatile guarantees latest value, not atomicity of the 3 steps.
What is CAS?::Compare-and-swap: if the value still equals what I read, swap it; else retry. Lock-free atomicity.