SwadhyAI Notes

All Low-Level Design notes

Concurrency · LLD

Atomicity and memory visibility

Atomicity means another thread never sees a half-done update. Visibility means a finished write on one thread becomes readable on another. Example: Gate A marks spot 101 occupied. Gate B on another core still prints “101 is free.” Or worse: Gate B sees occupied = true but the ticket id is still null, because the two writes did not become visible together.

Those two problems are easy to mix up. means the other thread never sees a half-done change. means a finished write on one thread becomes readable on another. You can fail either one and still get two cars in 101, or a crash on a null field.

The real question

Did I do one step — and can the other gate see it?

Problem A

Atomicity

Check-then-act is not one step. Reading occupied and then writing occupied is two steps, even on one line of English. increment++, ticket.status = PAID after a read, map.get then map.put — all of these can split.

Atomicity is about the whole decision, not the assignment. A boolean write may be one word. “If free, occupy” is still two words.

Problem B

Visibility

Thread A wrote occupied = true. Thread B may still see false, for a long time, unless something creates a hand-off: a lock both threads use, a volatile field, or an atomic variable.

A is quieter than a double ticket. Tests on one machine with one core often hide it. Production has many cores.

A finished picture, or a torn one

Gate A writes two fields: occupied = true, then ticketId = T-1. Without a handshake, Gate B can see any mix: both old, both new, or occupied true with ticketId still null. That last mix is how a null-pointer “impossible” crash is born.

Both old

occupied false, ticketId null. Gate B thinks 101 is free. Stale, but consistent.

Torn

occupied true, ticketId null. Gate B crashes or prints a blank slip.

Both new

occupied true, ticketId T-1. Only this is a finished occupy.

A lock around both writes, or a single atomic status that implies “occupied with this ticket,” keeps the picture whole. Publishing one volatile flag after the other fields is a pattern; publishing the flag first is a bug.

Visible, still a race

makes one field’s writes visible. It does not glue two actions into one. Reason about that before you reach for AtomicBoolean: if both gates still pass the if, you have a clean view of a lost update.

java
class ParkingSpot {
  private volatile boolean occupied = false;

  boolean tryOccupy() {
    if (occupied) return false;   // Gate A and Gate B both see false
    occupied = true;              // both write true
    return true;
  }
}

One field, one step

After the story is “only one occupy wins,” is a fair first Java sentence. You name the expected state and the next state. If the current value is not expected, you fail. The loser does not pretend it won.

java
class ParkingSpot {
  private final AtomicBoolean occupied = new AtomicBoolean(false);

  boolean tryOccupy() {
    return occupied.compareAndSet(false, true);
  }
}

// Gate: if (!spot.tryOccupy()) try the next rectangle.

A lock around “if !occupied then occupied = true” is the pessimistic twin. Both treat the decision as one step. Neither is “set the boolean and hope.” Visibility comes with the CAS or the lock; you do not add volatile on top “to be safe.”

The handshake, in plain language

Java will not promise that Gate B sees Gate A’s writes unless a edge exists. You do not need to lecture the JMM. You need one honest handshake:

  • Same lock. Unlock after the write. The next lock sees the write. Both gates must lock the same object — the spot or the lot, not “this” on a random controller.
  • Volatile / atomic field. A write to that field publishes. A later read of that field sees it. Other fields written before the volatile write can travel with it if you ordered them correctly.
  • Safe publication. A final field set in a constructor, then the object handed to another thread through a concurrent queue, is a finished picture. Mutating after the hand-off reopens the story.

Questions that come up

Is volatile enough for occupy?

No. It publishes the write. It does not make check-then-act one step. Use CAS or a lock for the decision. Use volatile when one thread writes a flag and others only need to see that flag, with no further “then do X if still Y.”

Is a long write atomic?

A 64-bit write can tear on some JVMs unless the field is volatile or you use AtomicLong. Even if the write is one word, “read, add, write” is still three steps. Atomicity of a store is not atomicity of a counter.

I assigned occupied on Gate A. Why does Gate B disagree?

Without a handshake, Gate B is allowed to keep a cached false. “I set it” is not “they can see it.” Add a lock both use, a volatile, or an atomic. Then re-ask whether the decision itself is still two steps.

How do I publish occupied and ticketId together?

Write the payload first, then a volatile status last — or wrap both in one immutable snapshot and CAS a reference. Or lock across both writes. Do not publish the status first and fill the id later.

increment++ is not one step either

A parking lot that counts free spots with a plain int and free++ has the same two problems. The add is a read, an add, and a write. Two gates can both read 12 and both write 13. Even if the store of the int is one word, the decision is three. And without a handshake, Gate B may not see Gate A’s 13 at all.

java
int free = 12;          // shared, not atomic, not visible
free++;                 // read 12, add, write 13 — two gates both write 13

AtomicInteger freeSpots = new AtomicInteger(12);
freeSpots.decrementAndGet();   // one step, handshake included

Counting free spots is a second shared fact besides occupancy. Prefer not keeping a redundant counter you must keep in sync with the board. If you must, the counter’s story is the same as occupy: one step, visible.

How to explain this in an interview

“Check-then-act is two steps, so I will CAS occupy. volatile would only publish the flag; both gates could still pass the if. If I write ticketId and occupied, I write the payload first and publish status last, or I CAS a snapshot.” That sentence beats a JMM lecture. If they ask how cores disagree, say: without a handshake the other core is allowed to keep a stale picture, including a torn mix of two fields.

Common mistakes

Treating volatile as a lock

volatile publishes a write. It does not make “if free then occupy” one step. Two gates can still both pass the if.

Assuming the other thread sees your assignment

Without a lock, a volatile, or an atomic, Gate B may keep reading the old occupancy. “I set it” is not “they can see it.”

Reciting the JMM instead of the parking story

The round is LLD. Say: one step, and a handshake so the other gate sees it. Save happens-before graphs for a follow-up if they ask.

Two cores, one Saturday

Picture Gate A on core 0 and Gate B on core 1. Each core has a cached copy of occupied. A writes true into its cache. Until a handshake, B’s cache may still hold false. That is visibility. Now picture A writing ticketId then occupied, and B reading occupied then ticketId. B can see true and null. That is a torn picture. CAS on a single AtomicReference to a small snapshot of occupied plus ticketId glues the pair. A lock around both writes glues the pair. Two volatiles do not, unless you order payload first and flag last, and B reads flag first then payload.

You do not need to draw CPU store buffers in an interview. You need to refuse the claim “I assigned it, so everyone sees it.” The machine did not promise Gate B a finished occupy.

Exercises

  1. volatile occupied plus if (!occupied) occupied = true. Which problem did you fix? Which remains?
  2. Gate A writes ticketId then occupied. Gate B reads occupied then ticketId. When is the torn picture possible?
  3. Replace the boolean with AtomicReference of a snapshot that holds both occupancy and ticket id. What did you glue?

Read next

Where the handshake still slips

Double-checked locking on a non-volatile singleton still publishes a torn lot. A volatile array of spots does not make each ParkingSpot’s fields visible. You published the array reference, not occupy. Final fields in a constructor, then unsafely publishing this, still escape. The handshake has to cover the writes you care about.

Takeaway

Atomicity is one step. Visibility is a handshake so the other core sees a finished picture. volatile publishes; it does not occupy. CAS or a lock can do both for a single flag. Two fields still need an order, a snapshot, or a monitor.