skip to content

How does the object-level lock used by an instance synchronized method differ from the class-level lock used by a static synchronized method, and why does it matter?

level: middleimportance: must knowfreq 64%

answer

  1. Instance method → this (per-object); static method → Class object (shared)
  2. Instance and Class locks are independent — neither blocks the other
  3. Static lock guards static state; instance lock guards instance state
  4. Mixed access to one field must agree on ONE lock
  5. this and ClassName.class are siblings, not parent/child

basics

~20 s

An instance synchronized method locks on that one object (this), so each instance has its own lock. A static synchronized method locks on the Class object, shared by all instances. The two locks are independent: a thread in a static synchronized method does not block a thread in an instance synchronized method.

solid answer

~50 s

An instance synchronized method acquires the monitor of the specific instance (this); two different instances have two different monitors, so threads on different objects never block each other. A static synchronized method acquires the monitor of the single Class object (e.g. Account.class), which is shared across all instances and protects static (class-level) state. The crucial point is that these are two distinct, independent locks. A thread holding the Class lock does not exclude a thread holding an instance lock, and vice versa. So if both static and instance methods touch the same shared data, synchronizing only one level leaves a race — they must agree on a single lock. This is a common bug: people assume 'it's all synchronized' without noticing the static method guards the Class monitor while the instance method guards a per-object monitor.

code

java · 14 lines
java
class Cache {
    private static final java.util.Map<String,String> map = new java.util.HashMap<>();

    // locks Cache.class
    static synchronized void putStatic(String k, String v) { map.put(k, v); }

    // locks 'this' -- a DIFFERENT monitor: races with putStatic!
    synchronized void putInstance(String k, String v) { map.put(k, v); }

    // Fix: make both agree on ONE lock object
    void putFixed(String k, String v) {
        synchronized (map) { map.put(k, v); }
    }
}

go deeper

for a junior

Knows instance methods lock the object and static methods lock the class, and that static is shared across instances.

for a middle

States clearly that the two locks are independent and that mixing static+instance synchronization on the same data is a race.

for a senior

Designs the locking policy by mapping each piece of shared state to exactly one guarding lock and audits all access paths for consistency.

for a principal

Establishes and documents a project-wide lock-ordering / guarded-by discipline, evaluates classloader implications for the Class lock, and weighs splitting locks for scalability versus a single coarse lock for correctness.

## Two kinds of state Java objects have two kinds of mutable state: - **Instance state** — fields that belong to a particular object (one copy per `new`). - **Static (class) state** — `static` fields, one copy shared by every instance of the class. Each needs its own protection, and `synchronized` picks the lock based on whether the method is static. ## Object-level lock (instance methods) ```java class Counter { private int n; synchronized void inc() { n++; } // locks 'this' } ``` The lock is the monitor of the **receiver instance**, `this`. If you create two counters `a` and `b`, `a.inc()` and `b.inc()` use **different** monitors and can run truly in parallel — which is correct, because they touch different `n` fields. Mutual exclusion only applies *per instance*. ## Class-level lock (static methods) ```java class Counter { private static int total; static synchronized void bump() { total++; } // locks Counter.class } ``` The lock is the monitor of the **`Class` object** `Counter.class`. There is exactly one `Class` object per class per classloader, so this single lock is shared by *all* calls regardless of instance. That is exactly right for protecting `static` fields, which are also shared. ## The locks are INDEPENDENT — the key insight The per-instance monitor and the `Class` monitor are **completely different locks**. Consequences: - A thread inside `bump()` (holding `Counter.class`) does **not** block a thread inside `a.inc()` (holding `a`'s monitor). They proceed concurrently. - Therefore, if a static method and an instance method both touch the **same** shared field, synchronizing each on its own default lock does **not** make them mutually exclusive — you have a race. Example bug: ```java class Cache { private static Map<K,V> map = new HashMap<>(); static synchronized void putStatic(K k, V v) { map.put(k, v); } // locks Cache.class synchronized void putInstance(K k, V v) { map.put(k, v); } // locks this — DIFFERENT lock! } ``` Two threads can corrupt `map` because the two methods hold different monitors. The fix is to make both synchronize on the **same** object — e.g. both `static synchronized` (Class lock), or both use an explicit `synchronized(map)` / a dedicated lock object. ## Mental model Think of it as: instance lock = `this`; class lock = `ClassName.class`. They are siblings, not parent/child. Choosing *which* lock guards a piece of state is the real design decision: shared (static) state → Class-level or a shared lock; per-object state → the instance lock. ## Why it matters Getting the lock object wrong is one of the most common concurrency bugs: code *looks* synchronized everywhere, but two access paths use different monitors, so the mutual exclusion you think you have doesn't exist. Always ask: 'do all access paths to this data hold the same lock?'

  • Two threads call a static synchronized method and an instance synchronized method on the same object at the same time. Do they exclude each other?
    No. The static method holds the Class monitor and the instance method holds the object's monitor — two different locks — so both run concurrently. If they touch the same data, that's a race.
  • If shared state is accessed by both a static and a non-static method, how do you protect it correctly?
    Make every access path synchronize on the same lock object — e.g. all on ClassName.class, or all on a single dedicated lock field — so the two paths truly exclude each other.

saying these in an interview costs you the question

  • Assuming a static synchronized method and an instance synchronized method on the same class mutually exclude — they use different monitors.
  • Thinking the Class lock is 'stronger' or contains the instance lock; they are independent siblings.
  • Protecting static state with an instance lock (this) — different instances would use different locks and not exclude each other.
  • Believing synchronizing 'everything' is enough without checking that all paths use the SAME lock object.

context