This deep dive explains the problem model, concurrency contract, Java implementation, and real-world caveats you should know before using this pattern in production.
Problem description:
We want to understand how a simple mutual-exclusion lock can be built using wait() and notify.
What we are solving actually:
We are solving thread ownership of a critical section. The purpose is educational: to understand the mechanics behind locking before relying on the JDK implementations.
What we are doing actually:
- Block a thread while the lock is owned.
- Wake waiting threads when the owner releases the lock.
- Add owner tracking to prevent invalid unlocks.
flowchart LR
A[Thread calls lock] --> B{Lock free?}
B -->|Yes| C[Acquire lock]
B -->|No| D[wait()]
C --> E[Critical section]
E --> F[unlock()]
F --> G[notify / notifyAll]
G --> A
Lock implementation
public class Lock {
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException {
while (isLocked) {
wait();
}
isLocked = true;
}
public synchronized void unlock() {
isLocked = false;
notify();
}
}
Usage
lock.lock();
try {
// critical section
} finally {
lock.unlock();
}
Improvement for production
The simple version does not track ownership. A safer version checks owner thread:
private Thread owner;
public synchronized void lock() throws InterruptedException {
while (isLocked) {
wait();
}
isLocked = true;
owner = Thread.currentThread();
}
public synchronized void unlock() {
if (Thread.currentThread() != owner) {
throw new IllegalMonitorStateException("Current thread does not own lock");
}
isLocked = false;
owner = null;
notify();
}
In real code, prefer ReentrantLock unless this is for learning.
Why This Lock Is Still Incomplete
Even with owner tracking, this custom lock is still missing features expected in production:
- reentrancy (same thread acquiring lock multiple times)
- timed acquisition (
tryLock(timeout)) - interruptible acquisition policy
- condition queues (
Condition) for coordinated waiting
Implementing these correctly is non-trivial, which is why JDK locks are preferred.
Reentrant Behavior Sketch (Learning Only)
private Thread owner;
private int holdCount;
public synchronized void lock() throws InterruptedException {
Thread current = Thread.currentThread();
while (owner != null && owner != current) {
wait();
}
owner = current;
holdCount++;
}
public synchronized void unlock() {
if (Thread.currentThread() != owner) throw new IllegalMonitorStateException();
holdCount--;
if (holdCount == 0) {
owner = null;
notifyAll();
}
}
This demonstrates reentrancy mechanics, but still lacks timeout/condition support.
Production API Equivalent (ReentrantLock)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class AccountService {
private final Lock lock = new ReentrantLock(true); // fair lock
private int balance = 0;
public void deposit(int amount) {
lock.lock();
try {
balance += amount;
} finally {
lock.unlock();
}
}
}
If you need separate read/write access, use ReadWriteLock. For optimistic reads on highly contended state, evaluate StampedLock.
Common Pitfalls
- Calling
unlock()from non-owner thread. - Using
notify()where multiple waiters need wake-up progression. - Holding lock during slow I/O.
- Forgetting
finallyaround unlock paths.
A lock implementation should optimize for correctness and debuggability first.
Testing Strategy
- concurrent stress test for race conditions
- interruption tests while waiting on lock
- reentrancy tests if supported
- ownership violation tests (
unlockby wrong thread)
Debug steps:
- test unlock by a non-owner thread and confirm it fails
- inspect whether
notify()should really benotifyAll()for the chosen design - keep lock usage wrapped in
try/finallyso failures do not strand waiters - prefer
ReentrantLockin real code once the learning goal is satisfied
Key Takeaways
- Correctness comes before throughput in concurrent code.
- Prefer proven JDK concurrency utilities in production over custom implementations.
- Always account for interruption, waiting conditions, and race windows.
- Build custom locks only for learning or highly specialized runtime behavior.
Comments