skip to content

Concurrent Collections

The thread-safe collections in java.util.concurrent — concurrent maps, copy-on-write lists, blocking queues and skip lists — and when each is the right tool. Interviewers use them to check you know why Collections.synchronizedMap is not a substitute.

part ofJavaoverview, primer and where to startread it →
on this pageshow

explore

questions

20

What is a BlockingQueue and what problem does it solve compared to a regular queue?

level: juniorimportance: must knowfreq 78%

answer

  1. Thread-safe queue + blocking on empty/full
  2. put/take block, offer/poll don't (timed variants too)
  3. Bounded = backpressure, no OOM
  4. No null elements (null = 'nothing available')
  5. Work queue behind ThreadPoolExecutor

basics

~20 s

A BlockingQueue is a thread-safe queue where a thread that takes from an empty queue waits until an item arrives, and (if bounded) a thread that adds to a full queue waits until space frees up. It makes producer-consumer hand-off safe and simple.

solid answer

~40 s

BlockingQueue is a thread-safe queue in java.util.concurrent designed for producer-consumer coordination. Beyond ordinary thread safety, it adds blocking: take() waits when the queue is empty until an element appears, and put() waits when a bounded queue is full until space frees up. This lets producers and consumers hand off work without writing manual wait/notify, locks, or busy-polling loops, and a bounded queue provides backpressure so fast producers can't exhaust memory. Implementations include ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, DelayQueue and PriorityBlockingQueue. It also offers non-blocking offer/poll, timed offer/poll, and is the work queue behind ThreadPoolExecutor. The interface forbids null elements, since null is used as a sentinel for 'nothing available'.

go deeper

for a junior

Knows it's a thread-safe queue that blocks on empty (take) and full (put), used for producer-consumer.

for a middle

Can name the four operation families (throws/special-value/blocks/timed) and explain backpressure from bounded capacity and the no-null rule.

for a senior

Connects it to ThreadPoolExecutor's work queue, reasons about choosing bounded vs unbounded for memory safety, and picks the right operation (put vs timed offer) per failure policy.

for a principal

Frames blocking queues within end-to-end backpressure/flow-control design, weighs them against reactive streams or lock-free alternatives, and sets capacity/rejection policy as a system-stability decision.

## The problem Imagine one or more **producer** threads creating work items and one or more **consumer** threads processing them. They need a shared buffer to pass items through. A plain `java.util.Queue` (like `ArrayDeque`) is **not thread-safe**: if two threads modify it at once you get corrupted state. Even a synchronized queue solves only *safety*, not *coordination*: what should a consumer do when the queue is empty? Spinning in a loop checking `isEmpty()` (busy-waiting) wastes CPU; rolling your own `wait()`/`notify()` is error-prone. ## What a BlockingQueue adds `BlockingQueue<E>` (in `java.util.concurrent`) is a `Queue` that is **thread-safe** AND **blocking**: - **`take()`** removes and returns the head; if the queue is **empty**, the calling thread *blocks* (sleeps efficiently) until an element becomes available. - **`put(e)`** inserts an element; if the queue is **full** (only relevant for bounded queues), the calling thread *blocks* until space frees up. 'Blocks' means the thread is parked by the JVM/OS and consumes no CPU until it's signaled — far better than busy-waiting. ## Four families of operations For each of insert and remove, BlockingQueue offers four behaviors when the operation can't proceed immediately: | | Throws exception | Returns special value | Blocks forever | Blocks with timeout | |---|---|---|---|---| | **Insert** | `add(e)` | `offer(e)` → boolean | `put(e)` | `offer(e, time, unit)` → boolean | | **Remove** | `remove()` | `poll()` → null if empty | `take()` | `poll(time, unit)` → null on timeout | | **Examine** | `element()` | `peek()` | — | — | - `put`/`take` are the *blocking* pair — the workhorses of producer-consumer. - `offer`/`poll` are *non-blocking*: they return immediately with a boolean / null instead of waiting. - The *timed* `offer`/`poll` wait up to a bound, then give up — useful to avoid hanging forever. ## Why bounded queues matter: backpressure A **bounded** queue has a fixed capacity. When it fills, producers calling `put` block. This is **backpressure**: a fast producer is throttled to the speed of consumers, so the queue can't grow without limit and exhaust memory. An **unbounded** queue (or an unbounded use of one) never blocks producers, which risks an `OutOfMemoryError` if producers outrun consumers. ## No null elements BlockingQueue **forbids `null`**. `poll()` returns `null` to mean 'nothing was available', so a stored `null` would be ambiguous. Adding `null` throws `NullPointerException`. ## Where it's used The most common real use is as the work queue inside a `ThreadPoolExecutor`: submitted tasks go into a BlockingQueue, and worker threads `take()` from it. It is the standard building block for pipelines and thread pools, so you rarely need manual locks for hand-off.

  • Why does BlockingQueue prohibit null elements?
    Because poll() returns null to signal 'queue empty / nothing available'. Allowing a stored null would make that return value ambiguous, so adding null throws NullPointerException.
  • What's the difference between put and offer?
    put blocks until space is available (or forever); offer returns immediately with false if it can't insert (or after a timeout in the timed variant).

saying these in an interview costs you the question

  • Claiming BlockingQueue busy-waits/spins — it parks the thread efficiently
  • Saying you can store null elements
  • Thinking every BlockingQueue is bounded (LinkedBlockingQueue defaults unbounded)
  • Confusing offer (non-blocking) with put (blocking)

context

open as a page

What is ConcurrentHashMap, and when would you choose it over a HashMap or a Collections.synchronizedMap?

level: juniorimportance: must knowfreq 80%

basics

~20 s

ConcurrentHashMap is a thread-safe map that many threads can read and write at once safely. Use it instead of HashMap (not thread-safe) or a synchronized map (locks the whole map) when several threads share a map.

open as a page

Compare ArrayBlockingQueue and LinkedBlockingQueue. When would you choose each?

level: middleimportance: must knowfreq 72%

basics

~20 s

ArrayBlockingQueue is backed by a fixed-size array and is always bounded. LinkedBlockingQueue uses linked nodes and is optionally bounded (unbounded by default). ArrayBlockingQueue uses one lock; LinkedBlockingQueue uses two (put and take), so it often has higher throughput under contention.

open as a page

Given the put/take/offer/poll family, how do you choose the right operation for a producer-consumer and handle interruption and timeouts?

level: middleimportance: must knowfreq 60%

basics

~20 s

Use put/take when blocking forever is acceptable, offer/poll when you must act immediately and not wait, and the timed offer/poll when you'll wait but only up to a limit. put and take throw InterruptedException, so handle interruption (usually by restoring the interrupt flag and stopping).

open as a page

What are the atomic compound methods on ConcurrentHashMap (putIfAbsent, computeIfAbsent, compute, merge), and why use them instead of get-then-put?

level: middleimportance: must knowfreq 75%

basics

~20 s

They do a check-and-update as one atomic step, so two threads can't interleave between checking and writing. A manual get-then-put has a race window where both threads see the same state and clobber each other; these methods close that window.

open as a page

Explain the snapshot iterator semantics of CopyOnWriteArrayList: why does iteration never throw ConcurrentModificationException, and what are the consequences?

level: middleimportance: must knowfreq 65%

basics

~20 s

When you start iterating, the iterator grabs the current array and walks that. Later changes go to a new array the iterator never sees, so it can't notice a modification and never throws ConcurrentModificationException. The downside: you may iterate stale data.

open as a page

What is CopyOnWriteArrayList and how does it differ from a regular ArrayList in a multithreaded program?

level: juniorimportance: should knowfreq 55%

basics

~20 s

CopyOnWriteArrayList is a thread-safe list. Every time you add or remove an element, it makes a fresh copy of the internal array. Reads happen on the array without locking, so many threads can read safely at the same time. A plain ArrayList is not thread-safe.

open as a page

Why does ConcurrentHashMap forbid null keys and null values, when HashMap allows them?

level: middleimportance: should knowfreq 62%

basics

~20 s

Because of an ambiguity: if get(key) returned null, you couldn't tell whether the key is absent or present with a null value. In a single thread you could re-check with containsKey, but under concurrency another thread could change the answer between calls, so nulls are banned outright.

open as a page

What is the precise cost model of CopyOnWriteArrayList writes, and what is CopyOnWriteArraySet?

level: middleimportance: should knowfreq 40%

basics

~20 s

Every write copies the whole array, so one write is O(n) in time and allocates a new array of size n. Adding n elements one by one is O(n squared). CopyOnWriteArraySet is the Set version with no duplicates, backed by a CopyOnWriteArrayList, so adds also scan for existing elements.

open as a page

What is ConcurrentLinkedQueue, and how does it achieve thread safety without locks?

level: middleimportance: should knowfreq 55%

basics

~20 s

It is a thread-safe, unbounded FIFO queue. Instead of locking, it uses atomic compare-and-swap (CAS) operations to update its head and tail, so many threads can add and remove items at once without blocking each other.

open as a page

What are ConcurrentSkipListMap and ConcurrentSkipListSet, and when would you use them?

level: middleimportance: should knowfreq 50%

basics

~20 s

They are thread-safe, sorted map and set classes — the concurrent versions of TreeMap and TreeSet. They keep elements in sorted order and can be used safely by many threads at once, supporting range queries like 'all keys between A and B'.

open as a page

How do DelayQueue and PriorityBlockingQueue differ from a plain FIFO BlockingQueue?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Neither is FIFO. PriorityBlockingQueue returns elements in priority order (a heap), and is unbounded. DelayQueue holds Delayed elements that only become available for take() after their delay expires; until then the queue acts empty even if it has elements.

open as a page

What is a SynchronousQueue and when is it the right choice?

level: seniorimportance: should knowfreq 55%

basics

~20 s

A SynchronousQueue has zero capacity: it holds no elements. Each put must wait for a matching take (and vice versa) — it's a direct hand-off between two threads. Use it when you want producers and consumers to rendezvous with no buffering.

open as a page

Explain ConcurrentHashMap's internal concurrency model in Java 8+: CAS on empty bins, per-bin locking, and how it differs from the older segmented design.

level: seniorimportance: should knowfreq 55%

basics

~20 s

In Java 8+, the map is one big array of buckets. Adding the first entry to an empty bucket uses a single atomic CAS instruction (no lock). Adding to a non-empty bucket locks just that bucket. Reads never lock. The old design instead split the map into a few fixed 'segments', each with its own lock.

open as a page

Compare CopyOnWriteArrayList with Collections.synchronizedList and a manually-locked ArrayList. When would you choose each?

level: seniorimportance: should knowfreq 50%

basics

~20 s

synchronizedList wraps a list so every method takes one lock, and you must still lock manually to iterate safely. CopyOnWriteArrayList instead copies the array on writes so reads need no lock and iteration is safe and never throws. Use COW when reads dominate; use synchronizedList or your own lock when writes are frequent.

open as a page

How do you decide between ConcurrentLinkedQueue and a BlockingQueue (e.g. LinkedBlockingQueue) for a producer/consumer pipeline?

level: seniorimportance: should knowfreq 58%

basics

~20 s

Use a BlockingQueue when consumers should wait for work and you want a size limit so producers slow down when it's full. Use ConcurrentLinkedQueue when you never want threads to block and don't need a bound — it just keeps growing and returns null when empty.

open as a page

What are 'weakly consistent' iterators in the java.util.concurrent collections, and how do they differ from fail-fast iterators?

level: seniorimportance: should knowfreq 47%

basics

~20 s

Weakly consistent iterators (used by concurrent collections like ConcurrentLinkedQueue and ConcurrentSkipListMap) let you keep iterating even while other threads change the collection, and never throw an error. Fail-fast iterators (used by ArrayList, HashMap) throw ConcurrentModificationException if the collection changes during iteration.

open as a page

ConcurrentHashMap makes each operation atomic, yet multi-key invariants and aggregate reads can still be wrong under concurrency. Where are the atomicity boundaries, and how do you design around them?

level: principalimportance: should knowfreq 42%

basics

~20 s

Each single operation (get, put, merge, etc.) is atomic, but a series of operations or anything spanning multiple keys is not. size() and bulk reads are only approximate while the map changes. For cross-key or whole-map consistency you need your own coordination, a snapshot, or a different design.

open as a page

From a design standpoint, when does CopyOnWriteArrayList's snapshot model break down, and what alternatives address those limits?

level: principalimportance: nice to knowfreq 28%

basics

~20 s

It breaks down when writes are frequent or the list is large (each write copies everything), when readers must see the very latest data (the snapshot can be stale), or when you must mutate during iteration. Then prefer ConcurrentHashMap-based structures, ConcurrentLinkedQueue, or a ReadWriteLock.

open as a page

Explain how a CAS-based lock-free queue like ConcurrentLinkedQueue makes progress, and what 'lock-free' guarantees (versus 'wait-free' and lock-based).

level: principalimportance: nice to knowfreq 33%

basics

~20 s

Each operation tries to update a pointer with compare-and-swap (CAS); if another thread changed it first, the CAS fails and the operation retries. 'Lock-free' means the system as a whole always makes progress even if some threads stall, but an individual thread can be made to retry indefinitely.

open as a page