DEV Community

Machine coding Master
Machine coding Master

Posted on • Originally published at javalld.com

Java Concurrency LLD: Master ReentrantLock and Condition for Precise Thread Signaling

Java Concurrency LLD: Master ReentrantLock and Condition for Precise Thread Signaling

In senior Java LLD interviews, designing thread-safe components like a bounded buffer or rate limiter requires precise thread signaling. If you are still relying on synchronized blocks and Object.wait(), you are likely failing the concurrency bar at Big Tech.

I built javalld.com while prepping for senior roles — complete LLD problems with execution traces, not just theory.

The Mistake Most Candidates Make

  • Using intrinsic locks with Object.wait(): This forces all threads (producers and consumers) into a single wait-set, requiring a broad notifyAll() that causes massive thread contention.
  • Ignoring spurious wakeups: Failing to re-evaluate the state condition in a loop when using time-bound wait methods.
  • Lacking time precision: Relying on Thread.sleep() or imprecise millisecond wait timeouts, which degrades system throughput under heavy load.

The Right Approach

  • Core mental model: Decouple the mutual exclusion lock from conditional signaling by using dedicated wait-sets for distinct business conditions.
  • Key entities/classes: ReentrantLock, Condition, TimeUnit.
  • Why it beats the naive approach: It eliminates spurious wakeup bugs and allows you to target and wake up only the exact subset of threads that can actually make progress.

The Key Insight (Code)

private final ReentrantLock lock = new ReentrantLock();
private final Condition notEmpty = lock.newCondition();

public Object poll(long timeout, TimeUnit unit) throws InterruptedException {
    lock.lock();
    try {
        long nanos = unit.toNanos(timeout);
        while (queue.isEmpty()) {
            if (nanos <= 0L) return null; // Timeout reached safely
            nanos = notEmpty.awaitNanos(nanos); // Spurious wakeup safe
        }
        return queue.poll();
    } finally {
        lock.unlock();
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Multiple Conditions per Lock: ReentrantLock allows creating separate Condition instances (e.g., notFull and notEmpty) to signal specific thread groups.
  • Time-Remaining Tracking: Condition.awaitNanos(nanos) returns the remaining time, letting you safely loop and resume waiting without losing track of the original timeout.
  • Targeted Signaling: Calling condition.signal() wakes up exactly one thread waiting on that specific condition, eliminating the CPU overhead of global notifyAll().

Full working implementation with execution trace available at https://javalld.com/learn/condition-await

Top comments (0)