Concurrency · Interleaving · Atomicity
Race Condition
Two threads share a counter. Run it; watch the final value drift. Add a lock; watch the variance go away.
Both threads do the same job: read the shared counter, add one, write it back, fifty times each. With one thread that's 100. With two threads and no coordination, the answer drifts — sometimes 100, sometimes 87, sometimes 64 — because the steps of one thread's increment slot in between the steps of the other's. Run it a few times, watch the histogram fan out, then flip the atomic switch and watch every run land on exactly 100.
Explore the activity to earn its passport stamp. Stamps record exploration, not mastery.
What’s happening under the hood
- ›The bug lives in the gap between read and write — a classic TOCTOU (time-of-check / time-of-use). Thread A reads 41, thread B reads 41, both compute 42, both write 42. One increment is lost. The hardware ran the code correctly; the program just told it to race.
- ›The atomic toggle adds a single global lock around the read/+1/write triple. The cost is real: threads now wait on each other inside the critical section, throughput drops, and a careless second lock can deadlock the program. Real systems use atomic CPU instructions (CAS, fetch-add) when they can — they're the same idea, baked into one uninterruptible memory operation.
- ›Modern CPUs and JIT compilers reorder reads and writes for speed; the memory model is the contract that says which reorderings a program can observe. Java's volatile, C++'s std::atomic, and JavaScript's SharedArrayBuffer + Atomics all exist because the naive 'just read and write the variable' you can write in a single thread is not safe across threads. The concept here is the floor; the memory model is the rest of the building.
Dig deeper
Phase 5 · ConcurrencyThe concept you just explored is taught with full depth in the formal DURA curriculum.