Transclude of java-concurrent-locks-lock-interface-basic.excalidraw
The Lock interface provides more flexible and sophisticated locking operations than intrinsic locks (synchronized). It’s part of java.util.concurrent.locks and serves as the foundation for ReentrantLock and ReentrantReadWriteLock.

Core Methods

lock()

Acquires the lock, blocking indefinitely until available.
If the lock is held by another thread, the current thread enters the waiting state and joins the lock’s Entry queue.

  • Ignores Interrupts
lock.lock(); // blocks until acquired, ignoring interrupts
try {
    // critical section
} finally {
    lock.unlock();  // Always unlock in finally block
}

lockInterruptibly()

Acquires the lock but can be interrupted while waiting.
Throws InterruptedException if the thread is interrupted.

try {
    lock.lockInterruptibly(); // can throw while waiting
    try {
        // critical section
		if (Thread.interrupted()){ // manually throw while running
			throw new InterruptedException();
		}
		// critical section
		// ...
    } finally {
        lock.unlock();
    }
} catch (InterruptedException e) {
    // Handle interruption
    Thread.currentThread().interrupt();
}

Use case: When you want threads to be responsive to cancellation requests.

tryLock()

Attempts to acquire the lock immediately without blocking.
Returns true if successful, false if lock is held by another thread.

if (lock.tryLock()) {
    try {
        // critical section
    } finally {
        lock.unlock();
    }
} else {
    // Lock not available, do something else
    System.out.println("Could not acquire lock, skipping operation");
}

Important!: tryLock() LockDoes not honour fairness settings, it will grab the lock immediately if available, even if other threads are waiting.

tryLock(long time, TimeUnit unit)

Unlike tryLock(), this variant is interruptible & thus the InterruptedException has to be handled.

Attempts to acquire the lock, waiting up to the specified timeout.
Returns true if acquired within the timeout, false otherwise.
Can be interrupted while waiting.

try {
    if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
        try {
            // critical section
        } finally {
            lock.unlock();
        }
    } else {
        // Timeout expired
        System.out.println("Could not acquire lock within timeout");
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

Use case: Deadlock prevention, timeout-based recovery strategies.

unlock()

Calling unlock() without holding the lock throws IllegalMonitorStateException

Releases the lock.
Must be called by the thread that acquired the lock.
Should always be in a finally block to ensure release even if exceptions occur.

lock.lock();
try {
    // critical section
} finally {
    lock.unlock();  // Guaranteed to execute
}

newCondition()

Returns a Condition instance bound to this Lock.
Provides await(), signal(), and signalAll() methods (similar to wait(), notify(), notifyAll()).

Lock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
 
// Producer
lock.lock();
try {
    while (queue.isFull()) {
        notFull.await();  // Wait until queue has space
    }
    queue.add(item);
    notEmpty.signal();  // Signal consumers
} finally {
    lock.unlock();
}
 
// Consumer
lock.lock();
try {
    while (queue.isEmpty()) {
        notEmpty.await();  // Wait until queue has items
    }
    item = queue.remove();
    notFull.signal();  // Signal producers
} finally {
    lock.unlock();
}

StdLib Interface Implementations (as of JDK25)

Best Practices

Critical Rules

  1. Always unlock in finally block - Prevents lock leaks on exceptions
  2. Unlock in same thread - The thread that acquires must release
  3. Don’t swallow InterruptedException - Restore interrupt status
  4. Use try-finally pattern - Even if you think no exception can occur

Lock vs Synchronized

Advantages:

  • Try lock without blocking
  • Timeout on lock acquisition
  • Interruptible lock acquisition
  • Multiple wait queues with condition queues
  • Fairness control
  • Lock/unlock in different methods Disadvantages:
  • Automatic unlock on exception
  • Worse performance in low-contention workloads
  • More complex & error prone

When to Lock vs Synchronized

Use Lock when you need:

  • Non-blocking lock attempts (tryLock())
  • Timed lock acquisition
  • Interruptible locking
  • Multiple condition queues
  • Fairness guarantees
  • Read-write lock semantics

Use synchronized when:

  • Simple mutual exclusion is sufficient
  • You want automatic lock management
  • Low-contention workloads

Thread States

Threads waiting for Lock implementations are in WAITING or TIMED_WAITING states (not BLOCKED).
The BLOCKED state is reserved for intrinsic locks (synchronized).