Synchronization (Types and When to Use What)
Coordinate concurrent access to shared state safely without killing performance.
synchronized for simple critical sections, Lock variants for advanced control, atomics for counters/CAS, and concurrent collections for shared containers.Problem it solves
- Multiple threads reading/writing the same mutable state can interleave unpredictably and produce wrong results (race conditions).
- Without proper coordination, one thread may not see the latest value written by another (visibility issue).
- Synchronization gives a safe way to protect critical sections and preserve invariants in concurrent code.
Why It Matters
- Without synchronization, race conditions produce incorrect and non-deterministic outcomes.
- Correctness bugs in concurrency are hard to reproduce and expensive in production.
- Right primitive selection balances safety, throughput, and maintainability.
When to Use
- Use
synchronizedas default for simple shared-state protection. - Use
ReentrantLockfor timeout, interruptible wait, fairness, or conditions. - Use atomics for counters and simple CAS transitions.
- Use concurrent collections instead of manually locking standard collections.
- Use semaphores to cap parallelism against limited downstream resources.
When NOT to Use
- Do not add synchronization when state is immutable or thread-confined.
- Avoid nested locks with inconsistent ordering (deadlock risk).
- Avoid over-synchronizing huge code blocks including blocking I/O.
Types of Synchronization and Selection Guide
| Aspect | What it gives | Use when |
|---|---|---|
synchronized (monitor lock) | Mutual exclusion + visibility, simple syntax | Small/clear critical sections, default choice |
ReentrantLock | Timed tryLock, interruptible lock, fairness, conditions | Need lock acquisition control or multiple condition queues |
ReadWriteLock | Many readers + exclusive writers | Read-heavy workloads with occasional writes |
StampedLock | Optimistic reads + read/write lock modes | Very read-heavy hot paths (advanced, careful validation needed) |
Atomic* classes | Lock-free atomic single-variable operations | Counters, flags, simple CAS-based state transitions |
volatile | Visibility/order only (no compound atomicity) | Stop flags, publication of immutable references |
| Concurrent collections | Thread-safe container operations | Shared maps/queues/lists without external coarse locking |
| Semaphores / Latches / Barriers | Coordination and throttling | Limit concurrency, await phases, one-time gates |
Common Confusions Cleared Up
volatile vs lockvolatile only guarantees visibility/ordering for single variable reads/writes. Locks are required for compound invariants and multi-step critical sections.
Atomic* vs synchronizedAtomics are great for single variable non-blocking operations. Once logic spans multiple fields/invariants, lock-based synchronization is safer.
In write-heavy workloads or small critical sections, overhead can outweigh benefits. Measure before adopting.
Variations / Implementations
1) Intrinsic lock (synchronized)
Best starting point for correctness and readability.
class Counter {
private int value;
synchronized void inc() { value++; }
synchronized int get() { return value; }
}2) Explicit lock (ReentrantLock)
Use when you need timeout/interrupt/fairness/conditions.
ReentrantLock lock = new ReentrantLock();
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// critical section
} finally {
lock.unlock();
}
}3) Read-write synchronization
Improves throughput when reads dominate writes.
ReadWriteLock rw = new ReentrantReadWriteLock();
rw.readLock().lock();
try { /* read shared state */ } finally { rw.readLock().unlock(); }4) Lock-free atomics
Use for high-frequency counters and simple atomic updates.
AtomicLong requests = new AtomicLong();
requests.incrementAndGet();5) Coordination primitives
Use semaphore/latch/barrier for flow control, not mutual exclusion.
Semaphore permits = new Semaphore(50); // max 50 concurrent tasks
permits.acquire();
try { callExternalApi(); } finally { permits.release(); }Common Mistakes / Anti-Patterns
- Locking on publicly accessible objects (external interference).
- Forgetting
unlock()infinallywith explicit locks. - Using
volatileforcount++and expecting atomicity. - Mixing multiple lock strategies on same state without clear policy.
- Ignoring contention metrics and lock wait time in production.
Interview Q&A
Quick Cheat Sheet
synchronized for simple correctness.ReentrantLock for timeout/interrupt/conditions.ReadWriteLock (measure before committing).Atomic* / volatile depending on atomicity needs.