AlgoInsight

Free lesson · Concurrency Interview Mastery

The java.util.concurrent Package Tour

Medium6 min readconcurrencymultithreadingjava

java.util.concurrent (everyone says "j.u.c") is one of the best-designed standard libraries in any language. It gives you battle-tested, high-performance concurrency building blocks so you almost never have to hand-roll a thread-safe data structure or a synchronizer. This lesson is a map of what's in it, organized by category, with the rule that matters most stated up front: before you write your own concurrent primitive, check whether j.u.c already has it. It almost always does, and theirs is more correct than yours will be.

Here's the territory at a glance:

mindmap
  root((java.util.concurrent))
    Executors
      ExecutorService
      ThreadPoolExecutor
      ScheduledExecutorService
      ForkJoinPool
    Concurrent Collections
      ConcurrentHashMap
      CopyOnWriteArrayList
      BlockingQueue family
      ConcurrentLinkedQueue
    Atomics
      AtomicInteger / Long
      AtomicReference
      LongAdder
    Synchronizers
      CountDownLatch
      CyclicBarrier
      Semaphore
      Phaser
    Locks
      ReentrantLock
      ReadWriteLock
      StampedLock
    Futures
      Future
      CompletableFuture
This mind map shows the major categories of the java.util.concurrent package. There are six branches. Executors include ExecutorService, ThreadPoolExecutor, ScheduledExecutorService, and ForkJoinPool. Concurrent collections include ConcurrentHashMap, CopyOnWriteArrayList, the BlockingQueue family, and ConcurrentLinkedQueue. Atomics include AtomicInteger, AtomicLong, AtomicReference, and LongAdder. Synchronizers include CountDownLatch, CyclicBarrier, Semaphore, and Phaser. Locks include ReentrantLock, ReadWriteLock, and StampedLock. Futures include Future and CompletableFuture.

Let's walk each category.

Executors - managing threads

Covered in the threading lesson, but to place them in the map: the Executors factory and ExecutorService interface let you submit tasks to managed thread pools instead of creating raw threads.

ExecutorService pool = Executors.newFixedThreadPool(8);
pool.submit(() -> doWork());
pool.shutdown();

Concurrent collections - thread-safe data structures

Never share a plain HashMap or ArrayList across threads without external locking. Use these instead - they're designed for concurrent access and far outperform wrapping a plain collection in synchronized.

ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
counts.merge("key", 1, Integer::sum);   // atomic increment of a map value

BlockingQueue<Task> queue = new LinkedBlockingQueue<>();
queue.put(task);          // blocks if full
Task t = queue.take();    // blocks if empty

Tip: ConcurrentHashMap.computeIfAbsent and merge are the idiomatic fix for check-then-act races on maps (the "get, check null, put" race). They perform the check-and-act atomically, so you don't need an external lock around map updates. Reaching for these instead of locking a plain map is a strong signal you know the library.

Atomics - lock-free single-variable updates

The java.util.concurrent.atomic package gives you variables you can update atomically without locks, using hardware compare-and-swap underneath. This is the right tool for counters and flags that multiple threads modify - the fix for the volatile count++ problem from the JMM lesson.

AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();              // atomic ++, no lock, no race

// Lock-free conditional update
counter.compareAndSet(5, 6);            // set to 6 only if currently 5

Note: Atomics are your lock-free building block. For a simple shared counter, AtomicInteger is both correct and faster than a lock. Under heavy contention from many threads, LongAdder is faster still. We go deep on the compare-and-swap mechanism behind these in the lock-free module - here, just know that "shared counter" should make you think AtomicInteger/LongAdder, not synchronized.

Synchronizers - coordinating threads

These coordinate when threads proceed, rather than guarding shared data. Each solves a specific coordination shape.

CountDownLatch ready = new CountDownLatch(3);
// worker threads call ready.countDown() when done initializing
ready.await();   // main thread blocks until all 3 have counted down

Semaphore permits = new Semaphore(10);   // allow 10 concurrent users
permits.acquire();
try { /* limited-capacity section */ } finally { permits.release(); }

Interview: A frequent disambiguation: CountDownLatch vs CyclicBarrier? A latch is one-shot and asymmetric - threads count down and waiters proceed when it hits zero; it can't be reset. A barrier is reusable and symmetric - all parties wait for each other and release together, then the barrier resets for the next round. Mnemonic: latch = "wait for N events to finish" (one time); barrier = "N threads rendezvous repeatedly." Naming that distinction cleanly is a reliable signal you know the synchronizers.

Locks - explicit locking (recap)

The locks subpackage, covered in the previous lesson: ReentrantLock (explicit, feature-rich exclusive lock), ReadWriteLock/ReentrantReadWriteLock (many readers or one writer), and StampedLock (adds optimistic reads). Reach here when synchronized lacks a feature you need.

Futures - results from async work

Future<V> represents a result that will be available later (from executor.submit). CompletableFuture<V> is the far more powerful async type - it lets you chain, combine, and compose asynchronous operations without blocking, and it gets its own full lesson next.

Putting pieces together

The tools compose. Here's a small, realistic snippet using several j.u.c categories at once - a pool, a concurrent map, an atomic, and a latch - to show how they fit:

ExecutorService pool = Executors.newFixedThreadPool(4);
ConcurrentHashMap<String, Integer> wordCounts = new ConcurrentHashMap<>();
AtomicInteger processed = new AtomicInteger();
CountDownLatch done = new CountDownLatch(documents.size());

for (String doc : documents) {
    pool.submit(() -> {
        for (String word : doc.split("\\s+")) {
            wordCounts.merge(word, 1, Integer::sum);   // atomic per-key update
        }
        processed.incrementAndGet();                   // lock-free counter
        done.countDown();                              // signal this doc is done
    });
}

done.await();          // block until every document is processed
pool.shutdown();
System.out.println("Processed " + processed.get() + " docs, "
                   + wordCounts.size() + " unique words");

No explicit locks anywhere, yet it's fully thread-safe: ConcurrentHashMap.merge handles per-key updates atomically, AtomicInteger handles the counter, the pool manages threads, and the latch coordinates "wait until all done." That's the j.u.c philosophy - assemble correct concurrency from well-tested parts rather than hand-rolling locks. This exact shape (pool + concurrent map + atomic + latch) is a strong template for "design a concurrent X" interview questions.

How to use this tour

You don't memorize every class - you build a mental index so that when a problem appears, the right tool comes to mind:

When you need... Reach for
A thread-safe map ConcurrentHashMap
A read-heavy, write-rare list CopyOnWriteArrayList
Producer-consumer handoff a BlockingQueue
A shared counter AtomicInteger / LongAdder
To wait for N tasks to finish CountDownLatch
Threads to rendezvous repeatedly CyclicBarrier
To cap concurrent access to a resource Semaphore
To run tasks on a pool ExecutorService
To compose async operations CompletableFuture

Interview: The meta-signal interviewers look for: do you reach for the library or reinvent it? When asked to design something concurrent, mentioning the right j.u.c class ("I'd back the producer-consumer with a BlockingQueue," "I'd use a ConcurrentHashMap with computeIfAbsent," "I'd cap concurrency with a Semaphore") signals real-world experience. Hand-rolling a thread-safe queue with wait/notify when BlockingQueue exists is a minor red flag - it suggests you haven't worked with the standard tools. Know the catalog; use the catalog.

That's the toolbox. The last two Java lessons cover the two newest, most powerful members in depth: virtual threads (Java 21's lightweight threads) and CompletableFuture (composable async). Those are next.

Back to Concurrency Interview Mastery