Definition
- Mutex – a locking mechanism that enforces mutual exclusion. In Java,
synchronizedandReentrantLockare mutexes. The thread that acquires the lock is the owner. Only the owner can release it. The lock is reentrant: the same thread can acquire it multiple times without deadlocking. - Semaphore – a signaling mechanism with a permit count.
acquire()takes a permit,release()returns one. ANY thread can release, regardless of who acquired. No ownership check, no reentrancy. - Binary Semaphore –
Semaphore(1)allows one thread at a time, resembling a mutex. But it lacks reentrancy (same thread acquiring twice deadlocks) and ownership (another thread can release it). - Key distinction – mutex = protection (keep others out of critical section). Semaphore = coordination (limit concurrent access or signal between threads).
A mutex is like a personal key to Rajesh's desk drawer at National Bank of India. Only Rajesh can lock and unlock his drawer with his key. He can open it multiple times (reentrant). Nobody else can use his key. A semaphore is like numbered tokens at the parking lot entrance. Anyone can take a token (acquire). Anyone can return any token (release, even one they did not take). Five tokens means five cars maximum. The token does not know who holds it.
Understanding the Concepts
What is a mutex?
A mutex enforces mutual exclusion: only one thread can hold the lock at a time, and only the owning thread can release it. In Java, both synchronized and ReentrantLock implement mutex semantics.
package com.bank.concurrency;
import java.util.concurrent.locks.ReentrantLock;
public class MutexDemo {
private static final ReentrantLock mutex = new ReentrantLock();
public static void main(String[] args) {
// Thread-A owns the lock
mutex.lock(); // holdCount = 1
mutex.lock(); // REENTRANT: holdCount = 2 (no deadlock)
mutex.unlock(); // holdCount = 1
mutex.unlock(); // holdCount = 0, released
// Thread-B trying to unlock Thread-A's lock:
// mutex.unlock(); → IllegalMonitorStateException: not the owner!
}
}
What is a semaphore?
package com.bank.concurrency;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
private static final Semaphore sem = new Semaphore(3); // 3 permits
public static void main(String[] args) throws InterruptedException {
sem.acquire(); // Thread-A takes a permit (3 → 2)
sem.acquire(); // Thread-A takes another (2 → 1)
// Thread-B releases a permit (1 → 2): ALLOWED! No ownership check!
new Thread(() -> {
sem.release(); // no exception! any thread can release
System.out.println("Thread-B released a permit");
}).start();
}
}
How are they compared?
| Feature | Mutex (Lock/synchronized) | Semaphore |
|---|---|---|
| Ownership | Yes (only owner unlocks) | No (any thread releases) |
| Reentrant | Yes | No (re-acquire blocks) |
| Concurrent holders | 1 (mutual exclusion) | N (counting permits) |
| Wrong-thread release | IllegalMonitorStateException | Silently succeeds |
| Purpose | Protect critical section | Limit access, signal between threads |
| Java class | ReentrantLock, synchronized | Semaphore |
| Banking use case | Balance update | Connection pool limit |
When should you use each?
Use a mutex when you need to protect a critical section where exactly one thread should execute at a time, and the same thread that enters must be the one that exits. Use a semaphore when you need to limit the number of concurrent accessors to a bounded resource or signal between threads.
package com.bank.concurrency;
import java.math.BigDecimal;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;
public class UseCaseDemo {
// Mutex: protect balance update (ownership matters)
private static final ReentrantLock balanceLock = new ReentrantLock();
private static BigDecimal balance = new BigDecimal("100000.00");
static void debit(BigDecimal amount) {
balanceLock.lock();
try {
balance = balance.subtract(amount);
} finally {
balanceLock.unlock(); // same thread that locked
}
}
// Semaphore: limit DB connections (counting, no ownership)
private static final Semaphore dbPool = new Semaphore(10);
static void query() throws InterruptedException {
dbPool.acquire();
try {
// max 10 concurrent DB queries
} finally {
dbPool.release();
}
}
}
Why should you never use binary semaphore as a mutex?
Because there is no ownership check, another thread can accidentally (or intentionally) release the semaphore, breaking mutual exclusion. Two threads would then be inside the critical section simultaneously.
What are common interview follow-up questions?
- Does Java have a Mutex class? No. synchronized and ReentrantLock serve as mutexes. Binary Semaphore is NOT a true mutex because it lacks ownership.
- Can a semaphore have more releases than acquires? Yes. If you call release() without a prior acquire(), the permit count increases beyond its initial value, which is usually a bug.
Visual Overview
Mutex vs Semaphore ══════════════════ Mutex (ReentrantLock): Thread-A: lock() → owns lock Thread-A: lock() → reentrant (holdCount=2) Thread-A: unlock() → holdCount=1 Thread-A: unlock() → released Thread-B: unlock() → IllegalMonitorStateException! Semaphore(3): Thread-A: acquire() → permits: 3→2 Thread-A: acquire() → permits: 2→1 Thread-B: release() → permits: 1→2 (no error!) Thread-C: release() → permits: 2→3 (no error!)
Key Takeaway
A mutex and a semaphore serve fundamentally different purposes despite surface similarities. A mutex (synchronized, ReentrantLock) is a locking mechanism with strict ownership: only the thread that locked it can unlock it, and it supports reentrancy. A semaphore is a signaling mechanism with a permit count: any thread can release a permit, there is no ownership check, and re-acquiring from the same thread blocks rather than succeeding.
Use a mutex for protecting critical sections where the same thread must enter and exit (balance updates). Use a semaphore for limiting concurrent access (connection pools, rate limiters) or signaling between threads (producer-consumer handoff). Never use a binary semaphore as a mutex replacement because the lack of ownership checking means another thread can accidentally release the permit, breaking mutual exclusion.
At National Bank of India, balance updates use ReentrantLock (ownership ensures only the updating thread releases), while the database connection pool uses Semaphore(10) (counting permits, no ownership needed).
Remember one sentence: a mutex locks with ownership (only the locker unlocks), a semaphore signals with permits (any thread can release), and they solve different problems.
---