Synchronization
A Race Condition: The Broken Counter
A broken counter — lost updates from a race condition
public class BrokenCounter {
private int count = 0;
void increment() {
count++; // read, add 1, write back — NOT atomic
}
int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
BrokenCounter counter = new BrokenCounter();
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("Expected: 200000");
System.out.println("Actual: " + counter.getCount());
}
}Expected: 200000 Actual: 187342
Fixing It With synchronized
The fix — synchronized method
public class SafeCounter {
private int count = 0;
synchronized void increment() {
count++; // now only one thread can execute this at a time
}
int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SafeCounter counter = new SafeCounter();
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("Actual: " + counter.getCount());
}
}Actual: 200000
synchronized Blocks
Synchronizing only the critical section
class SafeCounter {
private int count = 0;
private final Object lock = new Object();
void increment() {
// do any non-shared work here, unsynchronized
synchronized (lock) {
count++; // only this line needs protection
}
}
}Deadlock — A Brief Warning
Concept | Description |
|---|---|
Race condition | Unsynchronized threads interleave reads/writes to shared state, producing wrong results |
synchronized method | Only one thread at a time may execute this method on a given object |
synchronized block | Locks a specific object for just the critical section |
Intrinsic lock (monitor) | The built-in lock every object carries, used by synchronized |
Deadlock | Two or more threads wait on each other's locks forever |
Unsynchronized access to shared mutable state causes race conditions and lost updates
synchronized methods/blocks let only one thread execute the protected code on a given object at a time
Every object carries an intrinsic lock used by synchronized
Deadlock occurs when threads wait on each other's locks in a cycle — consistent lock ordering avoids it
java.util.concurrent offers higher-level alternatives worth learning next