import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class ConcurrentHashMapDemo {
private static final int THREAD_COUNT = 100;
private static final String KEY = "shared-key";
private static final String DIVIDER = "═".repeat(65);
private static final String THIN_DIVIDER = "─".repeat(65);
public static void main(String[] args) throws InterruptedException {
printHeader();
System.out.println("\n Running scenario 1: containsKey() + put() [NOT atomic]");
System.out.println(THIN_DIVIDER);
NotThreadSafeResult unsafeResult = runNotThreadSafe();
printResult(unsafeResult);
System.out.println("\n\n Running scenario 2: computeIfAbsent() [Atomic]");
System.out.println(THIN_DIVIDER);
ThreadSafeResult safeResult = runThreadSafe();
printResult(safeResult);
printSummaryTable(unsafeResult, safeResult);
}
// ----------------------------------------------------------------
// ❌ Scenario 1 — NOT Thread-Safe
// ----------------------------------------------------------------
private static NotThreadSafeResult runNotThreadSafe() throws InterruptedException {
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
AtomicInteger writeCount = new AtomicInteger(0);
AtomicInteger raceVictims = new AtomicInteger(0); // threads that overwrote a value already set
try (ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT)) {
CountDownLatch startGate = new CountDownLatch(1);
CountDownLatch endGate = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
final int threadId = i;
executor.submit(() -> {
try {
startGate.await();
if (!map.containsKey(KEY)) { // ← check
String prev = map.put(KEY, "T-" + threadId); // ← act
writeCount.incrementAndGet();
if (prev != null) {
raceVictims.incrementAndGet(); // overwrote someone else
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
endGate.countDown();
}
});
}
startGate.countDown();
endGate.await();
executor.shutdown();
}
return new NotThreadSafeResult(writeCount.get(), raceVictims.get(), map.get(KEY));
}
// ----------------------------------------------------------------
// ✅ Scenario 2 — Thread-Safe
// ----------------------------------------------------------------
private static ThreadSafeResult runThreadSafe() throws InterruptedException {
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
AtomicInteger writeCount = new AtomicInteger(0);
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
CountDownLatch startGate = new CountDownLatch(1);
CountDownLatch endGate = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
final int threadId = i;
executor.submit(() -> {
try {
startGate.await();
map.computeIfAbsent(KEY, k -> {
writeCount.incrementAndGet();
return "T-" + threadId;
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
endGate.countDown();
}
});
}
startGate.countDown();
endGate.await();
executor.shutdown();
return new ThreadSafeResult(writeCount.get(), map.get(KEY));
}
// ----------------------------------------------------------------
// Output helpers
// ----------------------------------------------------------------
private static void printHeader() {
System.out.println("\n" + DIVIDER);
System.out.println(" ConcurrentHashMap — Race Condition Demo");
System.out.println(" " + THREAD_COUNT + " threads hammering the same key simultaneously");
System.out.println(DIVIDER);
}
private static void printResult(NotThreadSafeResult r) {
System.out.printf(" %-30s %d%n", "Threads launched:", THREAD_COUNT);
System.out.printf(" %-30s %d ← should be 1%n", "Times put() was called:", r.writeCount);
System.out.printf(" %-30s %d ← previous value overwritten%n", "Silent overwrites:", r.raceVictims);
System.out.printf(" %-30s %s%n", "Final value in map:", r.finalValue);
System.out.println();
if (r.writeCount > 1) {
System.out.println(" 🔴 RACE CONDITION DETECTED");
System.out.println(" " + r.writeCount + " threads slipped through containsKey() simultaneously.");
System.out.println(" " + r.raceVictims + " of them silently overwrote an already-set value.");
System.out.println(" In prod: duplicate processing, corrupted cache, broken idempotency.");
} else {
System.out.println(" ⚠️ Got lucky this run — re-run to reliably reproduce the race.");
}
}
private static void printResult(ThreadSafeResult r) {
System.out.printf(" %-30s %d%n", "Threads launched:", THREAD_COUNT);
System.out.printf(" %-30s %d ← always exactly 1%n", "Times mapping fn ran:", r.writeCount);
System.out.printf(" %-30s 0 ← impossible by design%n", "Silent overwrites:");
System.out.printf(" %-30s %s%n", "Final value in map:", r.finalValue);
System.out.println();
System.out.println(" 🟢 THREAD-SAFE — GUARANTEED");
System.out.println(" computeIfAbsent() holds a lock at bucket level for the");
System.out.println(" entire check+insert. Other threads block, not bypass.");
}
private static void printSummaryTable(NotThreadSafeResult unsafe, ThreadSafeResult safe) {
System.out.println("\n\n" + DIVIDER);
System.out.println(" SUMMARY");
System.out.println(DIVIDER);
System.out.printf(" %-34s %-12s %-12s%n", "", "containsKey", "computeIf");
System.out.printf(" %-34s %-12s %-12s%n", "", "+ put()", "Absent()");
System.out.println(THIN_DIVIDER);
System.out.printf(" %-34s %-12s %-12s%n",
"Write count (expected: 1):",
unsafe.writeCount + (unsafe.writeCount > 1 ? " ❌" : " ✅"),
safe.writeCount + " ✅");
System.out.printf(" %-34s %-12s %-12s%n",
"Silent overwrites (expected: 0):",
unsafe.raceVictims + (unsafe.raceVictims > 0 ? " ❌" : " ✅"),
"0 ✅");
System.out.printf(" %-34s %-12s %-12s%n",
"Atomic check+insert:",
"NO ❌",
"YES ✅");
System.out.printf(" %-34s %-12s %-12s%n",
"Safe to use under concurrency:",
"NO ❌",
"YES ✅");
System.out.println(DIVIDER);
}
// ----------------------------------------------------------------
// Simple result holders
// ----------------------------------------------------------------
record NotThreadSafeResult(int writeCount, int raceVictims, String finalValue) {
}
record ThreadSafeResult(int writeCount, String finalValue) {
}
}Sample Output:
═════════════════════════════════════════════════════════════════
ConcurrentHashMap — Race Condition Demo
100 threads hammering the same key simultaneously
═════════════════════════════════════════════════════════════════
Running scenario 1: containsKey() + put() [NOT atomic]
─────────────────────────────────────────────────────────────────
Threads launched: 100
Times put() was called: 16 ← should be 1
Silent overwrites: 15 ← previous value overwritten
Final value in map: T-10
🔴 RACE CONDITION DETECTED
16 threads slipped through containsKey() simultaneously.
15 of them silently overwrote an already-set value.
In prod: duplicate processing, corrupted cache, broken idempotency.
Running scenario 2: computeIfAbsent() [Atomic]
─────────────────────────────────────────────────────────────────
Threads launched: 100
Times mapping fn ran: 1 ← always exactly 1
Silent overwrites: 0 ← impossible by design
Final value in map: T-0
🟢 THREAD-SAFE — GUARANTEED
computeIfAbsent() holds a lock at bucket level for the
entire check+insert. Other threads block, not bypass.
═════════════════════════════════════════════════════════════════
SUMMARY
═════════════════════════════════════════════════════════════════
containsKey computeIf
+ put() Absent()
─────────────────────────────────────────────────────────────────
Write count (expected: 1): 16 ❌ 1 ✅
Silent overwrites (expected: 0): 15 ❌ 0 ✅
Atomic check+insert: NO ❌ YES ✅
Safe to use under concurrency: NO ❌ YES ✅
═════════════════════════════════════════════════════════════════