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

Java multi-map invariants: one lock for a reservation boundary

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

A multi-map invariant relates values stored in several structures, so thread-safe individual map operations do not automatically make the combined update atomic.

Download Java source kit

Java 8+. The program uses JDK classes and requires no preview flags.

Choose the publication boundary

A stock map and a reservation map must agree about each accepted reservation. Decrementing stock before publishing a reservation lets a reader observe missing inventory with no corresponding owner. Publishing first exposes the opposite intermediate state.

This service owns both maps behind one lock. It checks quantity, availability and duplicate reference before changing either structure. The status operation acquires the same lock and returns a detached text result, preventing the caller from walking a live map outside the invariant boundary.

ConcurrentHashMap would make individual operations safe but would not join these two structures into a transaction. A database transaction or another protocol may be needed when ownership spans processes. This in-memory lock coordinates threads in one service instance only.

Reject without partial work

The failed reservation in the program leaves stock and reservation count unchanged. No external network call is performed under the lock. If an operation must contact a supplier, decide how pending state and compensation work; holding this lock indefinitely is not an application-wide transaction solution.

Working program

Java
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;
public class StockReservationBoundary {
    final ReentrantLock lock=new ReentrantLock();final Map<String,Integer> stock=new HashMap<>(),reserved=new HashMap<>();
    StockReservationBoundary(){stock.put("PACK",7);}
    boolean reserve(String reference,int units){
        if(reference==null||reference.isEmpty()||units<=0)throw new IllegalArgumentException("Reservation input");
        lock.lock();try{
            if(reserved.containsKey(reference)||stock.get("PACK")<units)return false;
            stock.put("PACK",stock.get("PACK")-units);reserved.put(reference,units);return true;
        }finally{lock.unlock();}
    }
    String status(){lock.lock();try{return "stock="+stock.get("PACK")+", reservations="+reserved.size();}finally{lock.unlock();}}
    public static void main(String[] args){StockReservationBoundary service=new StockReservationBoundary();System.out.println(service.reserve("R-71",8));System.out.println(service.status());System.out.println(service.reserve("R-71",3));System.out.println(service.status());}
}

Output

Output
false
stock=7, reservations=0
true
stock=4, reservations=1

Costs and boundaries

Under normal hash distribution the map operations have expected constant lookup work, but lock waiting can dominate latency. Memory grows with retained reservations. Completed state still needs an expiry or archival rule in a long-running service. This model does not provide crash recovery or a transaction across JVMs.

Common Mistakes

  • Readers must obey the same invariant boundary as writers.
  • Separate concurrent maps do not create a cross-map transaction.
  • Do not hold an application lock across unbounded external I/O.

Read next

Per-map operations, Lock ownership, Database transactions.

java
multi-index-invariants
Storage details