9.4 Concurrent Collections, Atomics, and Explicit Locks
Key Takeaways
- ConcurrentHashMap provides high-throughput thread safety using bucket-level fine-grained locking and CAS, strictly forbids null keys and values, and yields weakly consistent iterators that never throw ConcurrentModificationException.
- CopyOnWriteArrayList creates a fresh backing array upon every mutation, providing immutable snapshot iterators whose remove() method throws UnsupportedOperationException.
- Atomic classes in java.util.concurrent.atomic leverage low-level Compare-And-Swap (CAS) CPU instructions to perform lock-free, thread-safe mutations on single variables.
- ReentrantLock offers explicit locking with tryLock() timeouts, condition variables, and fairness options, while ReentrantReadWriteLock permits multiple concurrent readers but only a single exclusive writer.
- ReentrantReadWriteLock supports lock downgrading from write lock to read lock, but attempting to upgrade a read lock to a write lock causes a deadlock.
Concurrent Collections, Atomics, and Explicit Locks
High-performance concurrent applications require specialized data structures and synchronization primitives that avoid coarse-grained monitor bottlenecks. The java.util.concurrent package provides lock-free collections, copy-on-write collections, atomic variable wrappers, explicit locks, and thread coordination synchronizers.
1. Concurrent Collections vs. Synchronized Wrappers
Legacy synchronized collections (Collections.synchronizedMap(), Vector, Hashtable) synchronize every operation on a single, coarse-grained object monitor. This serializes all operations, creating severe contention under high concurrency, and still requires external synchronization when iterating.
In contrast, modern concurrent collections use lock striping, Compare-And-Swap (CAS) operations, and copy-on-write arrays to enable concurrent reads and writes without global locks.
2. Core Concurrent Collection Implementations
A. ConcurrentHashMap<K, V>
- Lock Granularity: Employs fine-grained locking on individual hash bucket bins using synchronized blocks and CAS operations. Reads are entirely non-blocking.
- Null Policy: Strictly forbids
nullkeys andnullvalues. Attempting to insert anullkey ornullvalue throws aNullPointerException! (Unlike standardHashMapwhich permits onenullkey and multiplenullvalues). - Atomic Compound Operations:
putIfAbsent(key, value): Inserts value only if key is not already mapped.computeIfAbsent(key, mappingFunction): Computes and inserts value atomically if key is absent.computeIfPresent(key, remappingFunction): Updates value atomically if key is present.merge(key, value, remappingFunction): Combines old and new values atomically.
- Weakly Consistent Iterators: Iterators reflect the map state at or after iterator creation, will never throw
ConcurrentModificationException, and can tolerate concurrent modifications.
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
// map.put(null, 1); // THROWS NullPointerException!
// map.put("KEY", null); // THROWS NullPointerException!
// Thread-safe atomic counter increment
map.merge("visitors", 1, Integer::sum);
B. ConcurrentSkipListMap<K, V> and ConcurrentSkipListSet<E>
- Structure: A thread-safe, scalable concurrent sorted map based on a probabilistic SkipList data structure.
- Ordering: Maintains elements in natural key order or according to a provided
Comparator. - Performance: Provides $O(\log n)$ time cost for
containsKey,get,put, andremove. - Null Policy: Strictly forbids
nullkeys andnullvalues (throwsNullPointerException).
C. CopyOnWriteArrayList<E> and CopyOnWriteArraySet<E>
- Design: Every mutative operation (
add(),set(),remove()) allocates a brand new copy of the underlying backing array. - Workload Fit: Highly optimized for read-heavy, write-rare scenarios (e.g., event listener lists, routing tables).
- Snapshot Iterators:
- Iterators operate on an immutable snapshot of the backing array captured at the instant the iterator was created.
- Concurrent modifications made to the list after iterator creation do not affect the iteration snapshot.
ConcurrentModificationExceptionis never thrown.- Iterator Mutations Forbidden: Invoking
iterator.remove(),iterator.add(), oriterator.set()throwsUnsupportedOperationException!
List<String> list = new CopyOnWriteArrayList<>(List.of("Alpha", "Beta"));
for (String item : list) {
list.add("Gamma"); // Modifies list, but does not affect the active iterator snapshot
// iterator.remove(); // THROWS UnsupportedOperationException!
}
System.out.println(list); // Prints: [Alpha, Beta, Gamma, Gamma]
D. BlockingQueue<E> Implementations & Operations Matrix
BlockingQueue implementations coordinate producer-consumer processing pipelines by blocking threads when attempting to insert into a full queue or retrieve from an empty queue.
| Queue Action | Throws Exception | Special Value | Blocks Indefinitely | Blocks with Timeout |
|---|---|---|---|---|
| Insert | add(e) (throws IllegalStateException) | offer(e) (returns false) | put(e) | offer(e, timeout, unit) |
| Remove | remove() (throws NoSuchElementException) | poll() (returns null) | take() | poll(timeout, unit) |
| Examine | element() (throws NoSuchElementException) | peek() (returns null) | N/A | N/A |
Common Implementations:
ArrayBlockingQueue: Bounded array-backed FIFO queue.LinkedBlockingQueue: Optionally bounded node-linked FIFO queue.PriorityBlockingQueue: Unbounded priority heap queue.SynchronousQueue: Zero-capacity direct handoff queue (each insert must wait for a concurrent take).
3. Atomic Variables and Compare-And-Swap (CAS)
The java.util.concurrent.atomic package (AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference<V>) provides lock-free, thread-safe single-variable mutations.
Hardware Compare-And-Swap (CAS)
CAS is a low-level CPU instruction that performs an optimistic read-modify-write:
If memory location $V$ still contains $\text{expectedValue}$, it updates $V$ to $\text{newValue}$ and returns true. If another thread modified $V$ in the interim, CAS returns false and the operation loops (spins) and retries without suspending the thread.
AtomicInteger counter = new AtomicInteger(0);
int next = counter.incrementAndGet(); // Atomic ++counter
int prev = counter.getAndIncrement(); // Atomic counter++
// Atomic conditional update
boolean success = counter.compareAndSet(2, 10); // If value is 2, sets to 10
// Atomic functional updates
counter.updateAndGet(x -> x * 2);
counter.accumulateAndGet(5, (x, y) -> x + y);
AtomicReference<V>
Used for atomic updates on custom immutable domain objects:
record UserConfig(String theme, int fontSize) {}
AtomicReference<UserConfig> configRef = new AtomicReference<>(new UserConfig("DARK", 14));
configRef.updateAndGet(current -> new UserConfig("LIGHT", current.fontSize()));
High-Contention Counters: LongAdder vs. AtomicLong
Under heavy multi-thread write contention, threads competing on a single AtomicLong spend excessive CPU cycles spinning on CAS retries. LongAdder distributes updates across an internal array of striped cell counters and sums them on sum(), eliminating contention bottlenecks.
4. Explicit Locks (java.util.concurrent.locks)
ReentrantLock
ReentrantLock implements the Lock interface, providing capabilities beyond intrinsic synchronized monitors:
Lock lock = new ReentrantLock(true); // 'true' enables FIFO fair acquisition ordering
lock.lock(); // Blocks until lock is acquired
try {
// Critical Section
} finally {
lock.unlock(); // ALWAYS unlock in a finally block!
}
Advanced ReentrantLock Capabilities:
- Non-Blocking Lock Acquisition (
tryLock()):if (lock.tryLock()) { try { ... } finally { lock.unlock(); } }prevents deadlocks. - Timed Lock Acquisition (
tryLock(timeout, unit)): Waits for lock up to a specified deadline. - Interruptible Acquisition (
lockInterruptibly()): Responds immediately toThread.interrupt()while waiting. - Condition Variables (
lock.newCondition()): Replaceswait()/notify()with multiple independent wait-sets per lock (await(),signal(),signalAll()).
ReentrantReadWriteLock
Maintains a pair of associated locks: a shared read lock and an exclusive write lock:
- Multiple threads can hold the read lock simultaneously when no write lock is held.
- Only one thread can hold the write lock (exclusive).
- Lock Downgrading IS Permitted: Hold write lock $ ightarrow$ acquire read lock $ ightarrow$ release write lock.
- Lock Upgrading is NOT Permitted: Holding a read lock and attempting to acquire a write lock causes a permanent deadlock!
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock rLock = rwLock.readLock();
Lock wLock = rwLock.writeLock();
// Lock Downgrading Example (Legal)
wLock.lock();
try {
// Modify shared state...
rLock.lock(); // Acquire read lock while holding write lock (Downgrade)
} finally {
wLock.unlock(); // Release write lock; still hold read lock
}
try {
// Read shared state...
} finally {
rLock.unlock();
}
5. Thread Coordination Synchronizers
| Synchronizer | Operational Model | Reusability |
|---|---|---|
CountDownLatch(int count) | Blocks threads at await() until countDown() is called $N$ times. | One-time use (count cannot be reset) |
CyclicBarrier(int parties, Runnable action) | Blocks $N$ threads until all $N$ arrive at await(). Executes barrier action, then releases all threads. | Reusable (automatically resets for next cycle) |
Semaphore(int permits) | Restricts access to a finite pool of $N$ shared resources via acquire() and release(). | Reusable permits pool |
CountDownLatch Example (One-Way Gate)
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Service initialized");
latch.countDown(); // Decrements count by 1
}).start();
}
latch.await(); // Main thread blocks until count reaches 0
System.out.println("All services initialized. System startup complete.");
CyclicBarrier Example (Multi-Phase Synchronization)
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("=== Phase Completed ==="));
Runnable worker = () -> {
try {
System.out.println("Phase 1 computation");
barrier.await(); // Waits for all 3 threads, triggers action, resets barrier
System.out.println("Phase 2 computation");
barrier.await(); // Reusable for phase 2!
} catch (Exception e) {}
};
A developer iterates through a CopyOnWriteArrayList<String> containing elements ["Alpha", "Beta", "Gamma"] using its iterator. During iteration, the loop calls iterator.remove(). What is the result?
Which of the following operations on a ConcurrentHashMap instance will throw a NullPointerException?
Which of the following statements accurately contrasts CountDownLatch with CyclicBarrier in Java?
A thread holds a read lock on a ReentrantReadWriteLock and attempts to acquire the associated write lock on the same thread without releasing the read lock first. What occurs?