Skip to content
AITroveRead. Build. Understand.
Make this comfortable

Java ReentrantLock: protect a complete state transition

Last updated: 29 Sept 20264 min read
tutorial
IntermediateBy AITrove Editorial

ReentrantLock provides explicit mutual exclusion; every access participating in an invariant must follow the same lock policy.

Java 8+. The program uses only JDK classes and runs without a framework.

Lock the relationship between fields

A stock ledger tracks available and reserved units. A reservation must check available stock, subtract from available and add to reserved as one protected transition. Locking only each field setter allows another worker to observe or act on an intermediate state.

The program owns one lock inside the ledger. It validates the requested quantity before locking because that check depends only on the argument. The availability decision and both field updates happen after acquisition. finally releases the lock even when a future edit adds an early return or throws an exception.

The snapshot method takes the same lock before reading both counters. A writer following the policy is insufficient if a reader ignores it. Returning a preformatted immutable string avoids exposing writable state. A richer API could return an immutable snapshot object with the two values instead.

Keep slow work outside the critical section

Do not hold this ledger lock while calling a remote payment service. The remote call can stall, fail or re-enter application code while every unrelated ledger operation waits. Decide on a reservation state transition first, then define compensating behaviour if the external step fails.

Two ledgers involved in a transfer introduce another issue: acquiring their locks in inconsistent order can deadlock. A stable lock ordering or a single transfer coordinator must cover that operation. The sample intentionally has one ledger and one invariant; it does not imply that arbitrary nested locks are safe. Read database transactions for persistent state boundaries.

Working program

Java
import java.util.concurrent.locks.ReentrantLock;
public class StockReservations {
    private final ReentrantLock lock = new ReentrantLock();
    private int available = 12;
    private int reserved;
    boolean reserve(int quantity) {
        if (quantity <= 0) throw new IllegalArgumentException("Positive quantity required");
        lock.lock();
        try {
            if (quantity > available) return false;
            available -= quantity;
            reserved += quantity;
            return true;
        } finally {
            lock.unlock();
        }
    }
    String snapshot() {
        lock.lock();
        try {
            return "available=" + available + ", reserved=" + reserved;
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        StockReservations stock = new StockReservations();
        System.out.println(stock.reserve(5));
        System.out.println(stock.reserve(20));
        System.out.println(stock.snapshot());
    }
}

Output

Output
true
false
available=7, reserved=5

Cost and failure boundaries

The state transition itself is O(1); waiting to acquire the lock depends on contention and scheduling. A fairness setting changes acquisition policy at a potential throughput cost, but it does not guarantee the operating system will schedule a particular thread promptly. Report operation latency rather than only time spent in the arithmetic.

One ledger retains O(1) counters and lock state. This version does not wait for unavailable stock: it returns false. A Condition can express waiting for a state change, but waits must occur in a predicate-checking loop and need interruption and timeout policies. Those policies belong to the application contract, not to unlock alone.

Common Mistakes

  • Always unlock in finally after successful acquisition.
  • Do not read protected fields through an unlocked side channel.
  • Do not hold a state lock during unrelated slow I/O.

Connect the contracts

Compare the boundary explained in One-variable updates with the assumptions made by this program.

java
locks
Storage details