An atomic variable supports indivisible operations on one stored value, including compare-and-set, without making every surrounding application step atomic.
Java atomic variables: compare-and-set and one-variable invariants
Java 8+. The program uses only JDK classes and runs without a framework.
Reserve before admitting work
A service allows at most three in-flight jobs. Reading an int and incrementing it later is not a safe reservation: several callers can all observe the same spare slot. The loop reads the current count and attempts to replace that exact observed value with the next count.
If another caller changes the value first, compareAndSet fails and the loop rereads state. Nothing has been reserved by that failed attempt. A successful caller must release its reservation in a finally block when its actual work ends. The program drives the gate deterministically to make the capacity and reuse behaviour easy to check.
This counter covers admission count only. It does not own the job’s payload, enforce request deadlines or guarantee fairness among callers. A semaphore is often a simpler fit when the requirement is just a permit count. The custom gate is shown to explain the compare-and-set boundary, not to replace every existing coordination utility.
A retry may repeat a calculation
Update functions used by atomic operations can be reapplied when an update loses a race. Keep them free of effects such as charging a card, writing a receipt or incrementing another unrelated counter. Compute the candidate value without changing external state; commit the single stored value through the atomic operation.
Changing a separate completed counter after decrementing inFlight creates a two-variable observation window. Readers can observe those changes at different times. An immutable compound state updated through one reference, or a lock around the invariant, is needed if the two numbers must always agree. Read visibility and ownership before treating atomics as a blanket synchronization policy.
Working program
import java.util.concurrent.atomic.AtomicInteger;
public class AdmissionGate {
private final AtomicInteger inFlight = new AtomicInteger();
private final int limit;
AdmissionGate(int limit) {
if (limit < 1) throw new IllegalArgumentException("Positive limit required");
this.limit = limit;
}
boolean reserve() {
for (;;) {
int observed = inFlight.get();
if (observed >= limit) return false;
if (inFlight.compareAndSet(observed, observed + 1)) return true;
}
}
void release() {
for (;;) {
int observed = inFlight.get();
if (observed == 0) throw new IllegalStateException("No reservation");
if (inFlight.compareAndSet(observed, observed - 1)) return;
}
}
public static void main(String[] args) {
AdmissionGate gate = new AdmissionGate(3);
System.out.println(gate.reserve());
System.out.println(gate.reserve());
System.out.println(gate.reserve());
System.out.println(gate.reserve());
gate.release();
System.out.println(gate.reserve());
}
}Output
true
true
true
false
trueCost and failure boundaries
The gate stores O(1) state. An uncontended call performs a small fixed amount of work, but retries under contention mean a successful call has no fixed retry bound. This sample makes no fairness or per-caller completion guarantee. Benchmark the actual contention pattern before choosing a coordination mechanism for a busy service.
Every success requires exactly one matching release. A double release can consume another caller’s reservation even if a zero check exists. Production code may wrap permits in scoped ownership objects rather than expose a naked release method. Validation of call discipline is separate from correctness of the atomic primitive.
Common Mistakes
- Do not release a reservation that was never acquired.
- Do not put external side effects inside a retryable update.
- Do not combine several atomic fields and assume their relationship is atomic.
Connect the contracts
Compare the boundary explained in Multi-field invariants with the assumptions made by this program.
