A multi-map invariant relates values stored in several structures, so thread-safe individual map operations do not automatically make the combined update atomic.
Java multi-map invariants: one lock for a reservation boundary
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
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
false
stock=7, reservations=0
true
stock=4, reservations=1Costs 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.
