Skip to content

Instantly share code, notes, and snippets.

@PrabhatKJena
Created March 6, 2026 15:48
Show Gist options
  • Select an option

  • Save PrabhatKJena/b5f527b7735337de300dc0d60f36566f to your computer and use it in GitHub Desktop.

Select an option

Save PrabhatKJena/b5f527b7735337de300dc0d60f36566f to your computer and use it in GitHub Desktop.
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 ✅       
═════════════════════════════════════════════════════════════════
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment