skip to content

What is the difference between lock(), lockInterruptibly(), and tryLock() with respect to interruption and blocking?

level: seniorimportance: should knowfreq 44%

answer

  1. lock() = block forever, ignores interruption (sets flag on acquire)
  2. lockInterruptibly() = block but throw InterruptedException if interrupted
  3. tryLock() = no wait; tryLock(t,u) = timed AND interruptible
  4. On InterruptedException the lock was NOT acquired — don't unlock
  5. synchronized can't be interrupted out of its wait — this is why explicit locks exist

basics

~20 s

lock() waits for the lock no matter what and ignores interruption while waiting. lockInterruptibly() waits but throws InterruptedException if the thread is interrupted, so the wait can be cancelled. tryLock() doesn't wait at all (or only up to a timeout) and returns true/false.

solid answer

~50 s

These three are ReentrantLock's acquisition methods with different blocking and cancellation behavior. lock() blocks until it gets the lock and is uninterruptible while waiting — if you interrupt the thread, it keeps waiting and only sets the interrupt flag, which you see after acquiring. lockInterruptibly() blocks but responds to interruption by throwing InterruptedException, so a thread stuck waiting for a contended lock can be cancelled — vital for responsive shutdown or task cancellation. tryLock() doesn't block at all: it returns true if the lock was free, false otherwise; tryLock(time, unit) blocks only up to a timeout and is also interruptible. This interruptibility is something synchronized cannot offer — a thread blocked on a monitor can't be interrupted out of the wait. The pattern matters for building cancellable, deadlock-resistant systems where threads must not get permanently stuck acquiring a lock.

code

java · 13 lines
java
void doCancellableWork() {
    try {
        lock.lockInterruptibly();          // cancellable wait
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt(); // restore the flag; do NOT unlock
        return;                             // lock was never acquired
    }
    try {
        // critical section
    } finally {
        lock.unlock();                      // only reached after a successful acquire
    }
}

go deeper

for a junior

Knows lock() waits, tryLock() doesn't, and that lockInterruptibly() can be cancelled with InterruptedException.

for a middle

Correctly distinguishes the three methods' blocking/interruption behavior and does not unlock after an interrupted acquisition.

for a senior

Builds cancellable acquisition for shutdown/timeouts, handles InterruptedException by propagating or restoring the flag, and explains why synchronized can't offer this.

for a principal

Designs cancellation and timeout policy across a system, reasons about interruption propagation through layered blocking calls and executors, and chooses acquisition modes to guarantee liveness under shutdown and contention.

## Interruption, briefly **Thread interruption** is Java's cooperative cancellation mechanism. Calling `t.interrupt()` sets a boolean **interrupt flag** on thread `t`. Well-behaved blocking operations check this flag and abort by throwing `InterruptedException`. Interruption is *cooperative*: it requests, it doesn't force; code must be written to notice and respond. The point is to let one thread ask another to stop waiting and wind down — essential for timeouts, shutdown, and cancelling long tasks. ## The three acquisition methods `ReentrantLock` offers three ways to take the lock, differing on **whether they block** and **whether they respond to interruption**: ### 1. `lock()` — block, ignore interruption ```java lock.lock(); try { /* ... */ } finally { lock.unlock(); } ``` Blocks until the lock is acquired. If the thread is interrupted *while waiting*, `lock()` does **not** abort — it keeps waiting, and merely **remembers** the interrupt: once it finally acquires the lock, the thread's interrupt flag is set so later code can notice. Use when acquisition is expected to be quick and you don't need to cancel the wait. ### 2. `lockInterruptibly()` — block, respond to interruption ```java try { lock.lockInterruptibly(); // throws if interrupted while waiting try { /* ... */ } finally { lock.unlock(); } } catch (InterruptedException e) { // cancelled while waiting — clean up, propagate, or restore flag } ``` Blocks like `lock()`, but if the thread is interrupted while waiting it **throws `InterruptedException`** and abandons the attempt (it did *not* acquire the lock, so you must **not** call `unlock()` on that path). This makes a thread that's stuck waiting on a contended lock **cancellable** — the foundation of responsive shutdown and avoiding threads wedged forever on a lock. ### 3. `tryLock()` / `tryLock(time, unit)` — non-blocking / timed ```java if (lock.tryLock()) { ... } // no wait at all if (lock.tryLock(2, TimeUnit.SECONDS)) { ... } // wait up to 2s, interruptible ``` The no-arg form never blocks: `true` if free, `false` otherwise. The timed form blocks up to the deadline, returns `false` on timeout, and **also throws `InterruptedException`** if interrupted while waiting (so it is both timed and interruptible). ## Summary table | Method | Blocks? | Interruptible while waiting? | Returns / throws | |---|---|---|---| | `lock()` | yes, indefinitely | **no** (sets flag, keeps waiting) | void | | `lockInterruptibly()` | yes, indefinitely | **yes** | throws `InterruptedException` | | `tryLock()` | no | n/a (doesn't wait) | `boolean` | | `tryLock(t, u)` | up to timeout | **yes** | `boolean` / throws `InterruptedException` | ## Why this beats synchronized A thread blocked trying to enter a `synchronized` block **cannot be interrupted** out of that wait — it waits until it gets the monitor, full stop. `lockInterruptibly()` and timed `tryLock` give you the escape hatch `synchronized` lacks, which is precisely why responsive, cancellable systems use explicit locks. ## Handling InterruptedException correctly When `lockInterruptibly()` or timed `tryLock` throws `InterruptedException`, don't swallow it. Either **propagate** it, or **restore the interrupt status** with `Thread.currentThread().interrupt()` so callers up the stack still see the cancellation request. Swallowing it silently defeats cooperative cancellation. Also remember: on the interrupted path the lock was **not acquired**, so there is nothing to `unlock()`. ## Choosing among them - Quick, uncontended, no cancellation needed → `lock()`. - Long or contended wait that must be cancellable (shutdown, request timeouts) → `lockInterruptibly()` or timed `tryLock`. - Don't-wait / take-an-alternative-path or multi-lock deadlock avoidance → `tryLock()`.

  • A thread is stuck waiting on a heavily contended lock during application shutdown and won't release. How do you make it cancellable?
    Acquire with lockInterruptibly() (or timed tryLock) instead of lock(). Then a shutdown coordinator can call interrupt() on the waiting thread, which makes the acquisition throw InterruptedException so the thread aborts the wait, cleans up, and exits — impossible with plain lock() or synchronized.
  • What happens to the interrupt flag if you interrupt a thread blocked in lock()?
    lock() keeps waiting; it does not throw. When it eventually acquires the lock it returns normally but leaves the thread's interrupt flag set, so code after the acquisition can check Thread.interrupted()/isInterrupted() and respond. The cancellation is deferred, not honored during the wait.

saying these in an interview costs you the question

  • Calling unlock() after lockInterruptibly() threw — the lock was never acquired
  • Swallowing InterruptedException instead of propagating or restoring the interrupt flag
  • Claiming lock() aborts on interruption — it does not; it keeps waiting and only sets the flag
  • Thinking a thread blocked on synchronized can be interrupted out of the wait — it cannot
  • Assuming no-arg tryLock() can throw InterruptedException — it doesn't wait, so it can't

context