Lesson 41.1 · Concurrency-Aware Data Structures
Race Conditions, Locks and Atomics
count++ is three steps (read, add, write); two threads can interleave them and lose updates. synchronized makes a block run one thread at a time; atomics do the same for single variables without a lock.
16 min
Think of it like this
Two cashiers updating the same paper ledger: both read "100", both add their sale, both write their own total. One sale disappears. A rule that only one cashier may hold the pen at a time fixes it.
1.What goes wrong
A race condition happens when the result depends on the timing between threads. count++ compiles to load, add and store; if two threads load the same value, one increment is lost. Without synchronisation there's also a visibility problem: one thread may not see another's write for a long time.
synchronized (or a ReentrantLock) gives mutual exclusion and visibility: only one thread runs the block at a time, and changes made inside are visible to the next thread that takes the lock.
Atomics (AtomicInteger, AtomicLong, LongAdder) use CPU compare-and-swap instructions: incrementAndGet() is a single indivisible step. They're faster than a lock for one variable, but can't make several variables change together.
volatile only guarantees visibility, not atomicity: a volatile int still loses updates with ++.
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
static int syncCount = 0;
static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
AtomicInteger atomic = new AtomicInteger();
Runnable work = () -> {
for (int i = 0; i < 100_000; i++) {
synchronized (lock) { syncCount++; } // one thread at a time
atomic.incrementAndGet(); // lock-free, indivisible
}
};
Thread a = new Thread(work), b = new Thread(work);
a.start(); b.start();
a.join(); b.join();
System.out.println("synchronized: " + syncCount);
System.out.println("atomic: " + atomic.get());
}
}Output
synchronized: 200000
atomic: 200000Remember
- Read-modify-write needs a lock or an atomic.
- volatile = visibility only.
- Keep critical sections short.
Common mistakes
- Synchronising on different lock objects in different methods.
- Calling slow I/O while holding a lock.
Words used in this lesson
- Thread
- An independent path of execution sharing the process's memory.
- Race condition
- A bug where the result depends on how threads happen to interleave.
- Mutual exclusion
- Only one thread may be inside a critical section at a time.
- Deadlock
- Threads waiting for each other's locks forever.