Coarse-grained synchronized vs fine-grained locking — whe… — Cracked Java
// Low-Level Design (LLD / OOD) · Concurrency Considerations in LLD
SeniorSystem Design

Coarse-grained synchronized vs fine-grained locking — when to choose each?

Once you've decided state must be guarded, the next decision is how much to lock at once. This is the coarse-versus-fine-grained trade-off, and articulating it is a reliable senior signal.

The two ends of the spectrum

Coarse-grained — one lock protects a large region or whole object. Simple, correct, easy to reason about; but it serializes everything and becomes a bottleneck under contention.

class ParkingLot {
    synchronized Ticket park(Vehicle v) {   // ONE lock for the whole lot
        Spot s = findFree(v);
        s.occupy();
        return new Ticket(s, v);
    }   // every gate waits on every other gate
}

Fine-grained — many small locks, each protecting a minimal slice, so independent operations proceed in parallel. More throughput; but more code, and a real risk of deadlock if locks are acquired in inconsistent order.

class Spot {
    synchronized boolean tryOccupy(Vehicle v) {   // lock per SPOT
        if (!free) return false;
        free = false; return true;
    }   // two cars taking different spots never block each other
}

How to choose

Low contention / simple invariant ......... COARSE (start here)
High contention, independent sub-units .... FINE  (lock per unit)
Mostly reads, rare writes ................. ReadWriteLock / StampedLock
Single number being updated ............... AtomicInteger (no lock)
Map/list of independent entries ........... concurrent collection
Decision heuristic

The pragmatic order: start coarse, refine only where contention is real. Premature fine-grained locking is its own anti-pattern — it adds deadlock risk and complexity for throughput you may not need.

The escalation ladder

  1. Coarse synchronized — default, correct, readable.
  2. Lock per independent unit (per spot, per account) — when units don't interact.
  3. ReadWriteLock / StampedLock — read-heavy, write-rare (a cache, a config).
  4. Atomics / CAS — a single counter or reference; lock-free.
  5. Concurrent collections — let ConcurrentHashMap etc. do the striping internally.

ReentrantLock vs synchronized

synchronized is simpler and enough for most LLD answers. Reach for ReentrantLock when you need a feature it gives you: tryLock (with timeout, to avoid deadlock), interruptible acquisition, fairness, or multiple condition variables (producer/consumer).

Mark your status