History of the question
Before Java 5, thread coordination relied on primitive methods like Thread.suspend (deprecated due to inherent deadlock risks) or Object.wait/notify, which required strict monitor ownership and suffered from lost wakeups if the notification occurred before the wait. With the introduction of java.util.concurrent in Java 5 (JSR 166), LockSupport was designed as a low-level unblocking primitive to enable the construction of high-performance synchronizers like AbstractQueuedSynchronizer, without the baggage of intrinsic locks.
The problem
In concurrent programming, a classic race condition occurs when a signaling thread invokes the unpark mechanism before the target thread actually parks. With traditional condition variables, this signal would be lost, causing the target thread to sleep indefinitely. A naive solution might use a counting semaphore to accumulate permits, but this introduces unnecessary complexity and potential resource leaks if the producer outpaces the consumer.
The solution
LockSupport employs a non-accumulating, single-bit permit associated with each thread. This permit acts as a disposable, thread-local gate pass:
Because the permit is not cumulative (it saturates at 1), it prevents memory leaks from excessive unparking while guaranteeing that one unpark issued before the park will be remembered, thus eliminating the lost wakeup problem through a happens-before relationship.
import java.util.concurrent.locks.LockSupport; public class PermitExample { public static void main(String[] args) throws InterruptedException { Thread worker = new Thread(() -> { System.out.println("Worker: Initial work..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Worker: Attempting to park..."); LockSupport.park(); System.out.println("Worker: Unparked successfully!"); }); worker.start(); // Signal before the worker actually parks Thread.sleep(50); System.out.println("Main: Calling unpark before worker parks"); LockSupport.unpark(worker); worker.join(); } }
Problem description
While designing a high-frequency trading system's order matching engine, we needed a backpressure mechanism where consumer threads could suspend processing when the inbound queue reached capacity, without holding locks that would prevent producers from checking queue state. Standard ReentrantLock with Condition created contention on the queue's lock during signaling, and Object.wait/notify suffered from the risk of lost wakeups during high-churn races.
Different solutions considered
1. Object.wait/notifyAll
This approach used the queue's intrinsic lock. Pros: Simple to implement using standard monitors. Cons: Required the producer to acquire the monitor to call notify, creating a serialization bottleneck. Worse, if a producer called notify during the brief window between the consumer checking the queue size and calling wait, the signal was lost, causing permanent consumer deadlock.
2. ReentrantLock with multiple Conditions
We attempted to use separate conditions for "full" and "empty" states. Pros: More flexible than intrinsic locks, allowing selective wakeups. Cons: Still required lock acquisition for signaling (signalAll), and the complexity of correctly transferring threads between condition queues introduced maintenance overhead without solving the fundamental locking overhead.
3. LockSupport with explicit atomic state
The chosen solution used an AtomicBoolean to represent "permission to proceed" and LockSupport for blocking. When the queue filled, the consumer atomically set a "needsParking" flag and then parked. Producers, after removing an item, checked the flag and called unpark if set. Pros: Signaling required no locks, eliminating contention during wakeups. The one-bit permit model ensured that even if the producer called unpark nanoseconds before the consumer called park (due to CPU scheduling), the wakeup was not lost.
Chosen solution and result
We selected the LockSupport approach. By decoupling the signaling mechanism from the queue's structural lock, we reduced producer latency by 40% under heavy load and eliminated the lost-wakeup scenarios observed during stress testing. The explicit state management (double-checking the condition after unpark) ensured correctness despite the spurious wakeup contract of park().
Does LockSupport.park release ownership of monitors held by the thread?
No. This is a critical distinction from Object.wait(). When a thread invokes LockSupport.park, it enters a waiting state but retains ownership of all monitors it currently holds. If another thread attempts to enter one of those monitors (e.g., a synchronized block on the same object), it will block, potentially causing a deadlock if the parked thread is the only one that could release it. Candidates often mistakenly assume park is like wait and releases locks; it is a purely thread-local scheduler primitive.
What is the behavior of LockSupport.park when invoked on a thread whose interrupt status is set?
The method returns immediately without blocking and does not clear the interrupt status. This differs fundamentally from Object.wait(), which clears the interrupt status and throws InterruptedException. With LockSupport, the thread must explicitly check and clear the interrupt status (via Thread.interrupted()) if it wishes to respect interruption conventions. This design allows park to be used in non-interruptible contexts or where interruption is handled as a separate concern from the parking permit.
How does LockSupport handle spurious wakeups, and how does this affect coding patterns?
LockSupport.park is documented to return "for no reason" (spurious wakeup), though in practice, this is rare on modern JVMs. Unlike the permit-based wakeup (unpark), spurious wakeups do not consume the permit. Therefore, the caller must always re-check the condition that caused the parking in a loop:
while (!canProceed()) { LockSupport.park(); }
Candidates often miss that simply checking the condition once after park is insufficient; the thread might wake up spuriously (or due to a stray interrupt) without an unpark call, requiring re-evaluation of the state condition. The permit ensures that a valid unpark is not lost, but it does not prevent spurious returns.