Careeroza
Race Conditions and synchronized
Race Condition
count++ looks like one step but is three: read count, add 1, write back. If two threads read the same old value at the same time, one update is lost. This is a race condition, and the wrong result changes from run to run.
Thread A: read count (5) write 6 Thread B: read count (5) write 6 Result: 6 (expected 7) ← one increment lost
class Counter {
int count = 0;
void increment() { count++; } // NOT thread-safe
}
// Two threads each call increment() 100_000 times.
// Expected 200000, typical results: 137412, 158903, 200000, 121076 ... (random)synchronized Fixes It
A synchronized method or block lets only one thread at a time hold the object's monitor lock. Other threads wait (BLOCKED). It also guarantees visibility: changes made inside are seen by the next thread that takes the same lock.
Example
Same program with a synchronized method: always 200000.
public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 100_000; i++) counter.increment();
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("Count = " + counter.get());
}
}
class Counter {
private int count = 0;
synchronized void increment() { count++; }
synchronized int get() { return count; }
}Output
Count = 200000
Method vs Block
| Form | Locks on | Use |
|---|---|---|
synchronized void m() | this | simple; locks the whole method |
static synchronized void m() | the Class object | protect static data |
synchronized (lockObj) { ... } | lockObj | lock only the critical part (better throughput) |
Tip: Use a private final Object lock = new Object(); for synchronized blocks, so outside code cannot lock on your object and cause surprises.