Explanation
What just happened
count++ looks like one step, but the CPU does three: read count, add 1, write it back. T2 read count before T1 had written its new value, so both threads started from 0 and both wrote 1. One update was lost.
Why it’s hard to catch
The bad interleaving needs both threads in the same tiny window. On a quiet laptop that’s rare, so tests pass. Under real load it happens all the time.
Try the what-if switch
Both fixes make read-add-write behave as one step, in different ways:
- synchronized: only one thread can be inside
increment(). T2 satBLOCKEDuntil T1 released the lock (the monitor), then read the fresh value. - AtomicInteger: no lock.
compareAndSetwrites only if the value is still what you read. T2’s CAS failed because T1 got there first, so T2 read again and retried. Nobody waited.
| synchronized | Atomic (CAS) | |
|---|---|---|
| Under contention | threads wait | threads retry |
| Protects | any block of code | one variable |
| Good for | several fields that change together | counters, flags, single values |
Rule of thumb
If two threads touch the same variable and at least one of them writes, you need synchronization. For a counter, incrementAndGet() does the CAS loop for you. Once two values must change together, like a balance and a log, use a lock: two separate atomics can’t keep them consistent.
Common questions
Why is count++ not thread-safe?
count++ is three steps: read, add 1, write. Two threads can both read the same old value and both write the same new one, so one increment is lost.
Should I use synchronized or AtomicInteger for a counter?
Both prevent lost updates. synchronized makes other threads wait for the lock. AtomicInteger uses compare-and-set: a thread whose value went stale retries instead of blocking. For a single counter use incrementAndGet(). For several values that change together, use a lock.
Does volatile make count++ thread-safe?
No. volatile makes writes visible to other threads, but count++ is still read-add-write, so two threads can still lose an update.
Verified: this timeline matches a real program, checked in CI on JDK 21 and 25. How we verify. Unclear or wrong? Tell us