A static synchronized method locks the Class object (ClassName.class), not any instance. That matters because static methods guard static (per-class) state, and there is exactly one Class object per class per classloader, so all threads contend on a single monitor regardless of how many instances exist.
The two distinct monitors
These lock completely different objects:
class Registry {
private static int total; // shared by ALL instances
private int local; // per instance
public static synchronized void addTotal() { // locks Registry.class
total++;
}
public synchronized void addLocal() { // locks 'this'
local++;
}
}
addTotal() is equivalent to synchronized (Registry.class) { total++; }.
Why it matters: the independence trap
Because the instance lock (this) and the class lock (ClassName.class) are different monitors, a static synchronized method and a non-static synchronized method do not exclude each other. One thread inside addTotal() and another inside addLocal() run concurrently.
Thread-A: addTotal() --> holds Registry.class
Thread-B: addLocal() --> holds someInstance
(no contention -> run in parallel)So if static mutable state is also reachable from a non-static method, locking only on this there is a bug — you must lock on the same monitor that guards that state (the class object).
Classloader caveat
"One Class object per class" is true per classloader. If the same class is loaded by two classloaders (web app redeploys, plugin frameworks), you get two distinct Class objects and therefore two distinct locks — the static lock no longer serializes across them. Rare, but a real source of subtle bugs in app-server environments.