Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

ยฉ 2026 Chaturmind. All rights reserved.

๐Ÿ‘ฅ10,104visitors

Built for engineers who want to go deep.


โ† Java Concurrency & Multithreading

Threads & Runnable

  • Introduction to Threads
  • ExecutorService & Thread Pools
  • Fork/Join Framework

Synchronization & Memory Model

  • synchronized and Locks
  • volatile and the Memory Model
  • CompletableFuture
  • Deadlock, Starvation, Livelock

Concurrent Collections

  • ConcurrentHashMap & CopyOnWriteArrayList
  • HashMap Concurrency Variants
  • Concurrent Utilities & Coordination
Chaturmind
โ† Java Concurrency & Multithreading

Threads & Runnable

  • Introduction to Threads
  • ExecutorService & Thread Pools
  • Fork/Join Framework

Synchronization & Memory Model

  • synchronized and Locks
  • volatile and the Memory Model
  • CompletableFuture
  • Deadlock, Starvation, Livelock

Concurrent Collections

  • ConcurrentHashMap & CopyOnWriteArrayList
  • HashMap Concurrency Variants
  • Concurrent Utilities & Coordination
HomeLearnJavaJava Concurrency & MultithreadingSynchronization & Memory Model
โœ“ FreeIntermediateยท 15 min read

synchronized and Locks

Prevent race conditions with synchronized blocks, ReentrantLock, and ReadWriteLock.

Published September 21, 2026


Synchronized and Locks in Java

When multiple threads access shared mutable state, you need mutual exclusion โ€” only one thread should modify the state at a time. Java offers two mechanisms: the synchronized keyword and the java.util.concurrent.locks package.

synchronized โ€” the basics

public class Counter {
    private int count = 0;

    // Method-level lock (locks on 'this')
    public synchronized void increment() {
        count++;
    }

    // Block-level lock (preferred โ€” smaller critical section)
    public void incrementBlock() {
        synchronized (this) {
            count++;
        }
    }

    // Static synchronized โ€” locks on the Class object
    public static synchronized void staticMethod() { ... }
}

Intrinsic Locks (Monitors)

Every Java object has an intrinsic lock (monitor). synchronized acquires this lock on entry and releases it on exit โ€” even if an exception is thrown.

// Two synchronized methods on the same object share the same lock
public class BankAccount {
    private double balance;

    public synchronized void deposit(double amount)  { balance += amount; }
    public synchronized void withdraw(double amount) { balance -= amount; }
    // deposit and withdraw cannot run concurrently on the same BankAccount
}

ReentrantLock โ€” explicit locking

import java.util.concurrent.locks.*;

public class Counter {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // ALWAYS unlock in finally!
        }
    }

    // Try to acquire lock without blocking
    public boolean tryIncrement() {
        if (lock.tryLock()) {
            try { count++; return true; }
            finally { lock.unlock(); }
        }
        return false;
    }

    // Try with timeout
    public boolean tryIncrementTimeout() throws InterruptedException {
        if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
            try { count++; return true; }
            finally { lock.unlock(); }
        }
        return false;
    }
}

ReentrantReadWriteLock โ€” multiple readers, exclusive writers

public class Cache<K, V> {
    private final Map<K, V> map = new HashMap<>();
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock  = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();

    public V get(K key) {
        readLock.lock();       // multiple threads can read simultaneously
        try { return map.get(key); }
        finally { readLock.unlock(); }
    }

    public void put(K key, V value) {
        writeLock.lock();      // exclusive write access
        try { map.put(key, value); }
        finally { writeLock.unlock(); }
    }
}

Condition Variables

public class BoundedQueue<T> {
    private final Queue<T> queue = new LinkedList<>();
    private final int capacity;
    private final Lock lock = new ReentrantLock();
    private final Condition notFull  = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    public void put(T item) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) notFull.await();
            queue.add(item);
            notEmpty.signal();
        } finally { lock.unlock(); }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) notEmpty.await();
            T item = queue.poll();
            notFull.signal();
            return item;
        } finally { lock.unlock(); }
    }
}

synchronized vs ReentrantLock

FeaturesynchronizedReentrantLock
Auto-unlock on exceptionโœ…โŒ (need finally)
Fairness policyNoYes (new ReentrantLock(true))
tryLock()Noโœ…
Multiple conditionsNoโœ…
Code readabilityBetterMore verbose

StampedLock โ€” a faster alternative for optimistic reads

ReentrantReadWriteLock lets multiple readers proceed concurrently, but every reader still has to acquire an actual read lock โ€” cheap, but not free, especially under very high read contention. StampedLock (Java 8+) adds a third mode, optimistic reading, that avoids acquiring a lock at all for the common case:

public class Point {
    private final StampedLock lock = new StampedLock();
    private double x, y;

    double distanceFromOrigin() {
        long stamp = lock.tryOptimisticRead(); // no actual lock taken
        double currentX = x, currentY = y;      // read without blocking anyone
        if (!lock.validate(stamp)) {            // did a writer sneak in while we read?
            stamp = lock.readLock();            // fall back to a real read lock
            try { currentX = x; currentY = y; }
            finally { lock.unlockRead(stamp); }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }

    void move(double deltaX, double deltaY) {
        long stamp = lock.writeLock();
        try { x += deltaX; y += deltaY; }
        finally { lock.unlockWrite(stamp); }
    }
}

tryOptimisticRead() doesn't block writers at all โ€” it just hands back a stamp. The read proceeds speculatively; validate(stamp) afterward checks whether a write happened in the meantime. If not, the optimistic read was safe and free of any locking cost; if a writer did intervene, the code falls back to a real (blocking) read lock. This trades a small amount of complexity for significantly better throughput under read-heavy, write-rare contention โ€” the exact profile ReentrantReadWriteLock already targets, just faster in the common case.

Interview Tips

  1. Deadlock: Thread A holds lock 1, wants lock 2; Thread B holds lock 2, wants lock 1. Prevention: always acquire locks in the same order.
  2. synchronized is reentrant โ€” a thread holding the lock can re-enter synchronized methods on the same object without blocking.
  3. volatile is NOT a replacement for synchronized โ€” it only guarantees visibility, not atomicity of compound operations.

Advertisement

Choosing the right tool, in words

The code above shows how each locking tool works. Choosing between them comes down to a few questions:

  • Can you avoid sharing mutable state at all? Immutable objects, thread confinement (each thread owns its data) and message passing need no locks. This is always the first option to consider.
  • Is it a single variable? Use an atomic class (AtomicInteger, AtomicReference, LongAdder) instead of a lock.
  • Is it a simple critical section? synchronized is concise, can't be left locked by a forgotten unlock(), and the JVM optimizes it well. It's the right default.
  • Do you need timeouts, interruptible waits, fairness, or several wait conditions? Use ReentrantLock with tryLock(timeout), lockInterruptibly(), or multiple Conditions.
  • Are reads frequent and writes rare? Consider ReentrantReadWriteLock, or StampedLock for optimistic reads. Measure first: read-write locks have more overhead, and only pay off when reads dominate and critical sections are non-trivial.
  • Is it a collection? Prefer ConcurrentHashMap, CopyOnWriteArrayList or a BlockingQueue over wrapping ordinary collections in locks.

Deadlock: how it happens and how to prevent it

A deadlock needs four conditions at once: mutual exclusion, holding one lock while waiting for another, no forced release, and a circular wait. The classic example is two transfers in opposite directions:

void transfer(Account from, Account to, long amount) {
    synchronized (from) {
        synchronized (to) {           // T1 locks A then waits for B; T2 locks B then waits for A
            from.debit(amount);
            to.credit(amount);
        }
    }
}

The standard fix breaks the circular wait with a global lock order:

void transfer(Account from, Account to, long amount) {
    Account first  = from.id() < to.id() ? from : to;     // always lock the lower id first
    Account second = first == from ? to : from;
    synchronized (first) {
        synchronized (second) {
            from.debit(amount);
            to.credit(amount);
        }
    }
}

Other defences:

  • Use tryLock with a timeout, and back off and retry if the second lock isn't available.
  • Keep critical sections short.
  • Never call unknown code (callbacks, listeners, remote services) while holding a lock.

To diagnose a deadlock in production, take a thread dump (jstack <pid>, or jcmd <pid> Thread.print): the JVM reports "Found one Java-level deadlock", and shows which threads hold and wait for which monitors.

Follow-up questions this topic invites โ€” and their answers

Q: Is synchronized slow? A: Not in the common case. An uncontended lock is cheap, and modern JVMs optimize synchronized blocks heavily. Performance problems come from contention: many threads competing for the same lock. Reduce contention (smaller critical sections, lock striping, concurrent collections), rather than just swapping synchronized for ReentrantLock.

Q: What does "reentrant" mean? A: A thread that already holds a lock can acquire it again without blocking. For example, a synchronized method can call another synchronized method on the same object. The lock keeps a hold count, and is released only when the count returns to zero. Both synchronized and ReentrantLock are reentrant.

Q: Why must unlock() always be in a finally block? A: If the code between lock() and unlock() throws, and unlock() isn't in finally, the lock is never released, and every other thread that needs it blocks forever. synchronized releases automatically when the block exits, even by exception, which is one reason it's the safer default.

Q: What's the difference between wait()/notify() and Condition.await()/signal()? A: Both let threads wait for a state change while releasing a lock. wait/notify work with an object's intrinsic monitor, which gives one wait set per object. Conditions belong to a Lock, and there can be several per lock, such as "notFull" and "notEmpty" in a bounded buffer, so you wake only the threads waiting for the relevant change. In both cases, always wait in a while loop that rechecks the condition, because of spurious wake-ups.

Q: What is lock striping? A: Splitting one lock into several, each guarding part of the data. For example, 16 locks, each protecting the keys that hash to it. Threads working on different parts don't contend. Older versions of ConcurrentHashMap used segments this way, and the current version locks individual bins.

Advertisement

Previous

Fork/Join Framework

Next

volatile and the Memory Model

AI Tutor

Lesson: synchronized and Locks

Quick actions

AI responses can be inaccurate. Verify critical information.