Chaturmind
LearnDSASystem DesignDevOpsEngineering 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

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

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.

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.