Back to Locks: ReentrantLock, ReadWriteLock, StampedLock
Q2Question 2 of 7

LockSupport.park() vs wait()?

Key Concepts
  • LockSupport.park() and unpark() are permit-based: each thread has a binary permit (0 or 1) that is consumed by park() and set by unpark().
  • wait() and notify() are monitor-based: the calling thread must hold the monitor lock and a notify() before wait() is permanently lost.
  • The critical advantage of park/unpark is that unpark() before park() is NOT lost because the permit is stored and park() returns immediately.
  • park/unpark do not require any lock, can target a specific thread, and never throw IllegalMonitorStateException.
  • AbstractQueuedSynchronizer (AQS), the foundation of ReentrantLock, CountDownLatch, and Semaphore, is built on park/unpark.
  • Both park() and wait() are subject to spurious wakeups, so the caller must always re-check the condition in a loop.
  • park() shows as WAITING (parking) in thread dumps, while wait() shows as WAITING (on object monitor).

Definition

LockSupport.park/unpark are permit-based (no lock needed, unpark before park is NOT lost), while wait/notify are monitor-based (lock required, notify before wait IS lost); AQS uses park/unpark internally.
  • park() blocks the current thread by consuming its permit. If the permit is already available (set by a prior unpark()), park() returns immediately without blocking.
  • unpark(thread) sets the target thread's permit to 1. If the thread is currently parked, it unblocks. If it is not yet parked, the permit is stored and the next park() returns immediately.
  • wait() releases the monitor lock and blocks until notify() or notifyAll() is called. The calling thread must hold the monitor (be inside a synchronized block), otherwise IllegalMonitorStateException is thrown.
  • notify() wakes one arbitrary thread waiting on the monitor. If no thread is waiting, the notification is silently lost.
  • Key difference – unpark() before park() stores the permit (signal not lost). notify() before wait() is permanently lost (signal lost).
  • AQS – AbstractQueuedSynchronizer, the foundation of ReentrantLock, CountDownLatch, Semaphore, and other j.u.c utilities, is built on park/unpark because they are more flexible and do not require monitor ownership.

At National Bank of India, wait/notify is like a teller sitting in the break room (wait) until the manager shouts "NEXT!" (notify). If the manager shouts before the teller sits down, the shout is lost and the teller sits in the break room forever. park/unpark is like a permission slip box: the manager puts a slip in Rajesh's box (unpark). When Rajesh checks his box (park), the slip is there, so he immediately goes to work. If the manager put the slip before Rajesh checked, the slip is waiting in the box. The signal is never lost.

Understanding the Concepts

What is LockSupport.park() and unpark()?

LockSupport provides thread-blocking primitives based on a binary permit. Each thread has exactly one permit, which is either 0 (not available) or 1 (available). park() consumes the permit: if it is 1, park() sets it to 0 and returns immediately. If it is 0, park() blocks until unpark() is called. unpark(thread) sets the target thread's permit to 1 and unblocks it if it is currently parked.

JAVA
package com.bank.concurrency;

import java.util.concurrent.locks.LockSupport;

public class ParkUnparkDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            System.out.println("Rajesh: before park");
            LockSupport.park();  // blocks until permit is available
            System.out.println("Rajesh: after park, resumed!");
        }, "Rajesh");
        worker.start();
        Thread.sleep(100);
        System.out.println("Manager: calling unpark");
        LockSupport.unpark(worker);  // sets permit, worker resumes
    }
}
TEXT
Rajesh: before park
Manager: calling unpark
Rajesh: after park, resumed!

Why is unpark() before park() not lost?

The permit is stored. If unpark() is called before the target thread calls park(), the permit is set to 1 and the subsequent park() consumes it immediately without blocking.

JAVA
package com.bank.concurrency;

import java.util.concurrent.locks.LockSupport;

public class UnparkBeforeParkDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread worker = new Thread(() -> {
            try { Thread.sleep(200); } catch (InterruptedException e) { return; }
            LockSupport.park();  // permit already set: returns IMMEDIATELY
            System.out.println("Rajesh: did not block! Permit was waiting.");
        }, "Rajesh");
        worker.start();
        LockSupport.unpark(worker);  // set permit BEFORE park() is called
        worker.join();
    }
}

Why is notify() before wait() lost?

With wait/notify, there is no stored signal. If notify() is called when no thread is waiting on the monitor, the notification is silently discarded and can never be recovered.

JAVA
package com.bank.concurrency;

public class LostNotifyDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        // Thread-1 notifies before Thread-2 waits
        new Thread(() -> {
            synchronized (lock) {
                lock.notify();  // nobody waiting: notification LOST!
            }
        }, "Notifier").start();

        Thread.sleep(100);  // ensure notify runs first

        new Thread(() -> {
            synchronized (lock) {
                try {
                    lock.wait();  // waits FOREVER: the notify was lost
                } catch (InterruptedException e) { }
            }
        }, "Waiter").start();
        // Waiter thread is stuck forever
    }
}

How is park/unpark different from wait/notify?

| Feature | park/unpark | wait/notify |
|---|---|---|
| Lock required? | No | Yes (synchronized) |
| Signal before wait? | NOT lost (permit stored) | LOST permanently |
| Target thread | unpark(specificThread) | notify() wakes any one |
| Spurious wakeup? | Yes (must check condition) | Yes (must check condition) |
| IllegalMonitorStateException? | Never | If not in synchronized |
| Thread dump state | WAITING (parking) | WAITING (on object monitor) |
| Used by | AQS, ReentrantLock, Latch | Classic monitor pattern |

How does AQS use park/unpark?

AbstractQueuedSynchronizer maintains a CLH queue of waiting threads. When a thread fails to acquire the lock, it is added to the queue and parked via LockSupport.park(). When the lock is released, the next thread in the queue is unparked via LockSupport.unpark(nextThread). This design does not require any monitor lock, can target specific threads, and never loses signals.

JAVA
// Simplified AQS acquire logic:
// if (!tryAcquire(arg)) {
//     enqueue(currentThread);
//     LockSupport.park(this);   // block until unparked
// }
// Simplified AQS release logic:
// if (tryRelease(arg)) {
//     Thread next = dequeue();
//     LockSupport.unpark(next);  // wake specific next thread
// }

What are common mistakes?

The most common mistake is forgetting that park() can return spuriously. Just like wait(), park() may return without unpark() being called. Always check the condition in a loop.

JAVA
// WRONG: assuming park() only returns on unpark()
LockSupport.park();
processTask();  // may run even if unpark was not called!

// CORRECT: check condition in a loop
while (!taskReady) {
    LockSupport.park();
}
processTask();

What are common interview follow-up questions?

  • Can you use park/unpark to implement a simple mutex? Yes, but it is complex and error-prone. Use ReentrantLock instead, which is built on AQS (which uses park/unpark internally).
  • Does park() respond to interrupts? Yes. If the thread is interrupted while parked, park() returns and the interrupt status is set. Check Thread.interrupted() after park().

Visual Overview

DIAGRAM
  park/unpark vs wait/notify
  ═════════════════════════

  park/unpark (permit-based):
    unpark(Rajesh)  →  permit = 1  (stored!)
    ...later...
    park()          →  permit = 1  →  consume → return immediately
    Signal NOT lost!

  wait/notify (monitor-based):
    notify()  →  nobody waiting  →  signal LOST!
    ...later...
    wait()    →  blocks forever  →  nobody to notify
    Signal LOST!

Key Takeaway

LockSupport.park() and unpark() are the modern building blocks for thread blocking in Java. They are permit-based: unpark() stores a permit, and park() consumes it. The critical advantage over wait/notify is that unpark() before park() is not lost because the permit is stored and the subsequent park() returns immediately. wait/notify has no storage: notify() before wait() is permanently lost.

All java.util.concurrent utilities (ReentrantLock, CountDownLatch, Semaphore) are built on AbstractQueuedSynchronizer, which uses park/unpark internally. This means that whenever you use a Lock or Latch, you are indirectly using park/unpark. The flexibility of targeting a specific thread, not requiring a monitor lock, and never throwing IllegalMonitorStateException makes park/unpark strictly superior for building concurrency utilities.

At National Bank of India, a custom task scheduler used wait/notify and occasionally lost notifications when the scheduler thread called notify() before the worker thread entered wait(). Switching to park/unpark eliminated the problem because the permit ensured the signal was never lost.

Remember one sentence: park/unpark stores the signal as a permit so it is never lost, while notify() before wait() is permanently discarded.

---

Share
Use ← → arrow keys to navigate