Why count++ loses updates

Read the explanation
What if

Two threads each run count++ once. The result can be 1, not 2.

Predict first

count starts at 0, and two threads each run count++ once. What can it print?

0123456789t1.start()t2.start()join()main: print countread 0add → 1write 1read 0add → 1write 1

Tap any block to see what that thread is doing.

  • Running
  • Waiting / parked
Step 1/6

main starts two threads. Each runs count++ once.

1static int count = 0;2 3static void increment() {4    count++; // read count, add 1, write count5}6 7public static void main(String[] args) throws Exception {8    Thread t1 = new Thread(Main::increment, "T1");9    Thread t2 = new Thread(Main::increment, "T2");10    t1.start();11    t2.start();12    t1.join();13    t2.join();14    System.out.println(count); // can print 115}

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 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