synchronized in Java
Intrinsic locks on methods and blocks, mutual exclusion, reentrancy.
Every object has a lock
Every Java object has an intrinsic lock (a monitor). synchronized (obj) { ... } acquires it, runs the block, and releases it, even if an exception is thrown. Only one thread can hold it at a time: mutual exclusion. And the next holder sees all the previous holder's writes.
private final Object lock = new Object();
private int count;
void inc() {
synchronized (lock) {
count++;
}
}Your turn
Two threads each increment n[0] 750 times, always inside synchronized (lock). What does main print?
int[] n = {0};
Object lock = new Object();
Runnable r = () -> {
for (int i = 0; i < 750; i++)
synchronized (lock) { n[0]++; }
};
Thread a = new Thread(r), b = new Thread(r);
a.start(); b.start();
a.join(); b.join();
System.out.println(n[0]);1500750A different number on each run
Show the answer
Always 1500. Every n[0]++ happens while holding the same lock, so the read-modify-write can't interleave and nothing is lost. After both join()s, main is guaranteed to see the final value.
Which lock does a method take?
A synchronized instance method locks on this. A static synchronized method locks on the Class object, Account.class. Those are two different locks, so the two methods do not exclude each other: a trap when both touch the same static field.
class Account {
// lock: this
synchronized void deposit() { }
// lock: Account.class
static synchronized void audit() { }
}Same lock, every access
Locking only the writes isn't enough. A reader without the lock has no visibility guarantee and may even see a HashMap mid-resize. Every access, reads included, must use the same lock. Also note: threads locking *different* objects (listA, listB) don't exclude each other at all.
synchronized void add(String k) {
stock.merge(k, 1, Integer::sum);
}
int count(String k) { // no lock: bug!
return stock.getOrDefault(k, 0);
}Can a thread block itself?
down() is synchronized and calls itself while already holding the lock. What happens?
class Counter {
synchronized int down(int n) {
return n == 0 ? 0 : 1 + down(n - 1);
}
}
void main() {
System.out.println(new Counter().down(3));
}3Hangs forever (deadlock)Throws IllegalMonitorStateException
Show the answer
It prints 3. Intrinsic locks are reentrant: a thread that already owns a lock can acquire it again without blocking itself. The JVM keeps a hold count and releases the lock when the count drops back to zero.
Keep critical sections small
Old classes like Vector, Hashtable and StringBuffer synchronize every method: safe, but slow under contention. That's why ArrayList, HashMap and StringBuilder are today's defaults. And never make a network call while holding a lock: every other thread piles up BLOCKED behind it.
Key takeaways
- A synchronized instance method locks on this
- A static synchronized method locks on the Class object
- Every access to shared state must use the SAME lock
- Reentrant: the owner can re-acquire without blocking itself
An uncontended lock is cheap: the JVM only builds a heavyweight monitor ("inflation") once threads actually compete for an object's lock.
Practice questions
What does this print?
int[] n = {0};
Object lock = new Object();
Runnable r = () -> {
for (int i = 0; i < 1000; i++)
synchronized (lock) { n[0]++; }
};
Thread a = new Thread(r), b = new Thread(r);
a.start(); b.start();
a.join(); b.join();
System.out.println(n[0]);- 2000
- 1000
- A different number on each run
- Compile error
Check your answer
2000. Every increment runs while holding the same lock, so no update is lost. After both joins, main is guaranteed to see the final value.
What does this print?
class Account {
synchronized void a() { b(); }
synchronized void b() {
System.out.println("b ran");
}
}
void main() {
new Account().a();
System.out.println("done");
}- b ran done
- It hangs forever (deadlock)
- Throws IllegalMonitorStateException
- done
Check your answer
b ran done. The thread already holds the Account's lock when a() calls b(). Intrinsic locks are reentrant, so it simply re-enters and the hold count goes up.