One kitchen, many cooks
Picture a small restaurant kitchen at 8 pm on a Saturday. Four cooks, one shared jar of salt, one stove, one recipe card on the wall, and a counter (called the pass) where waiters drop order tickets and cooks pick them up.
Now the simple version. If two cooks grab the salt jar at the same moment, they bump hands and spill it. If every cook waits for the jar to be free, nobody spills anything, but the kitchen is a bit slower. Almost all of concurrency is this trade: sharing things safely costs a little speed, and sharing them unsafely costs correctness.
The technical version: the cooks are threads, the kitchen is the process (one shared memory), the salt jar is shared mutable state, and "waiting for the jar" is a lock. A race condition is two cooks touching the jar at once; a deadlock is two cooks each holding something the other needs; the pass is a blocking queue; the number of cooks is the thread pool size.
That is why LLD rounds always end with "now make it thread-safe". This guide uses this one kitchen from the first section to the last interview answer, with Java 17+ code whose class names come straight from the kitchen. It connects to OOP and patterns and to the distributed version of the same problem in Consensus and locks.
Where the kitchen analogy stops. Real cooks see each other and avoid collisions by common sense. Threads do not: a CPU core may keep a thread's copy of a value in a register or cache, so one thread can fail to see another's update at all (the Java Memory Model section covers this). Also, a cook who is waiting is standing still, while a thread can be descheduled mid-step at any instruction. Keep both differences in mind.
1. Vocabulary, in kitchen words
| Term |
Kitchen picture |
Technical meaning |
| Process |
The whole restaurant: own walls, own stock |
Isolated memory and resources |
| Thread |
One cook inside the kitchen |
A path of execution that shares the process memory with its siblings |
| Concurrency |
One cook juggling soup and pasta, switching between them |
Managing many tasks that overlap in time |
| Parallelism |
Four cooks working at the exact same second |
Tasks literally running at once on multiple cores |
| Critical section |
"Using the salt jar" |
Code touching shared mutable state that must not run in two threads at once |
| Atomic |
A pinch that is taken in one motion, no one can interrupt halfway |
One indivisible step |
| Thread-safe |
A kitchen that serves correct dishes however the cooks' timing falls |
Correct under any interleaving |
Note that count++ is not atomic. It is three steps: read, add one, write. That detail causes the next section.
2. Race condition: two cooks, one salt-jar label
Simple version: the salt jar has a tally sticker, "pinches used: 41". Cook A reads 41. Cook B reads 41. Both add one and write "42". Two pinches went in, the sticker says one. Neither cook did anything wrong alone; the interleaving of their steps did.
Technical version: that is a lost update, the classic race condition. Here are the unsafe jar, and two fixes (Java 17):
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class SaltJarDemo {
// Unsafe: "read the sticker, then write the new sticker" is two steps.
static class UnsafeSaltJar {
private int pinches = 0;
void addPinch() { pinches++; } // read, add, write
int pinches() { return pinches; }
}
// Fix 1: only one cook holds the jar at a time.
static class LockedSaltJar {
private int pinches = 0;
synchronized void addPinch() { pinches++; }
synchronized int pinches() { return pinches; } // reads need the lock too
}
// Fix 2: the jar does the add as one indivisible step.
static class AtomicSaltJar {
private final AtomicInteger pinches = new AtomicInteger();
void addPinch() { pinches.incrementAndGet(); }
int pinches() { return pinches.get(); }
}
static void fourCooksAddPinches(Runnable addPinch) throws InterruptedException {
List<Thread> cooks = new ArrayList<>();
for (int i = 0; i < 4; i++) {
Thread cook = new Thread(() -> {
for (int n = 0; n < 100_000; n++) addPinch.run();
}, "cook-" + i);
cooks.add(cook);
cook.start();
}
for (Thread cook : cooks) cook.join();
}
public static void main(String[] args) throws InterruptedException {
UnsafeSaltJar unsafe = new UnsafeSaltJar();
fourCooksAddPinches(unsafe::addPinch);
System.out.println("unsafe: " + unsafe.pinches() + " (expected 400000)");
LockedSaltJar locked = new LockedSaltJar();
fourCooksAddPinches(locked::addPinch);
System.out.println("synchronized: " + locked.pinches()); // always 400000
AtomicSaltJar atomic = new AtomicSaltJar();
fourCooksAddPinches(atomic::addPinch);
System.out.println("atomic: " + atomic.pinches()); // always 400000
}
}
You've reached the free preview of this post.
Subscribe to the Blogs plan (₹99/mo) — or use any Algomi Suite, Career, Investor, or Trader plan — to keep reading.
See plans