Java synchronized keyword and race conditions
race condition, critical section, synchronized method, synchronized block, intrinsic lock, monitor, visibility problem, mutual exclusion, thread safety
Synchronization and Race Conditions
A race condition occurs when multiple threads read and modify shared data concurrently and the final result depends on scheduling โ not program logic.
The Problem
public class Counter {
private int count = 0;
public void increment() {
count++; // NOT atomic: read โ add 1 โ write โ 3 operations
}
public int get() { return count; }
}
// Two threads calling increment() 1000x โ result < 2000
Fix 1 โ synchronized Method
public synchronized void increment() {
count++; // only one thread at a time
}
Fix 2 โ synchronized Block (finer control)
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
// other non-shared work here โ not blocked
}
// Running 2 threads, 1000 increments each
Counter c = new Counter();
Thread t1 = new Thread(() -> { for (int i=0; i<1000; i++) c.increment(); });
Thread t2 = new Thread(() -> { for (int i=0; i<1000; i++) c.increment(); });
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(c.get()); // always 2000
Synchronization enforces mutual exclusion โ only one thread holds the lock at a time. It also establishes a happens-before relationship, ensuring visibility: all writes before synchronized exit are visible to the next thread that acquires the same lock.
Synchronization has a performance cost: acquiring and releasing a lock, plus memory flushing. Keep synchronized blocks as small as possible โ synchronize only the minimum code that accesses shared state, not entire methods when only a few lines need protection.
