Skip to content

Concurrent Collections

The java.util.concurrent package provides thread-safe collection implementations designed for concurrent access.

A thread-safe implementation of Map that allows concurrent reads and a high level of concurrent writes.

Key Characteristics:

  • Segments the map to allow multiple threads to modify different segments concurrently
  • No locking for read operations in most cases
  • Java 8+ implementation uses a more fine-grained approach with node-level locking
  • Does not allow null keys or values

Performance:

  • Read operations are typically not blocked
  • Write operations only lock a small portion of the map
  • Much better concurrent performance than Collections.synchronizedMap()
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// Thread-safe operations
concurrentMap.put("key1", 1);
concurrentMap.put("key2", 2);
// Atomic operations
concurrentMap.putIfAbsent("key1", 3); // Won't change existing value
Integer oldValue = concurrentMap.replace("key1", 4); // Returns previous value
boolean replaced = concurrentMap.replace("key1", 4, 5); // CAS operation
// Atomic computations
concurrentMap.compute("key1", (k, v) -> v == null ? 1 : v + 1); // Increment
concurrentMap.computeIfAbsent("key3", k -> k.length()); // Add if absent
concurrentMap.computeIfPresent("key1", (k, v) -> v * 2); // Double if present
// Bulk operations
concurrentMap.forEach((k, v) -> System.out.println(k + ": " + v));

ConcurrentHashMap vs HashMap with Synchronization

Section titled “ConcurrentHashMap vs HashMap with Synchronization”
FeatureConcurrentHashMapCollections.synchronizedMap(HashMap)
Locking granularitySegment/node levelWhole map
Read performanceNo locking for most readsLocks entire map
Write performanceLocks only affected segments/nodesLocks entire map
IterationWeakly consistent (reflects some but not all updates)Fail-fast (throws ConcurrentModificationException)
Null valuesNot allowedAllowed
Memory overheadHigherLower

A thread-safe variant of ArrayList where all mutative operations create a fresh copy of the underlying array.

Key Characteristics:

  • Immutable snapshots for iteration (no ConcurrentModificationException)
  • Best for read-heavy, write-rare scenarios
  • Thread-safe without locking for reads
  • High memory overhead for frequent modifications
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
copyOnWriteList.add("item1");
copyOnWriteList.add("item2");
// Safe iteration even if modified during iteration
for (String item : copyOnWriteList) {
System.out.println(item);
copyOnWriteList.add("newItem"); // Won't affect current iteration
}
// Size reflects changes after iteration completes
System.out.println(copyOnWriteList.size()); // 4 (item1, item2, newItem, newItem)

A Set implementation backed by CopyOnWriteArrayList.

Key Characteristics:

  • Same copy-on-write semantics as CopyOnWriteArrayList
  • Maintains insertion order like LinkedHashSet
  • Uses equals() for element comparison
Set<String> copyOnWriteSet = new CopyOnWriteArraySet<>();
copyOnWriteSet.add("item1");
copyOnWriteSet.add("item2");
copyOnWriteSet.add("item1"); // Duplicate, not added
// Safe iteration during modification
for (String item : copyOnWriteSet) {
System.out.println(item);
copyOnWriteSet.add("newItem"); // Won't affect current iteration
}

ConcurrentSkipListMap and ConcurrentSkipListSet

Section titled “ConcurrentSkipListMap and ConcurrentSkipListSet”

Thread-safe navigable map and set implementations based on skip lists.

Key Characteristics:

  • Maintains elements in sorted order
  • Expected O(log n) time for most operations
  • Lock-free implementation for high concurrency
  • Equivalent to concurrent TreeMap/TreeSet
// Concurrent sorted map
ConcurrentNavigableMap<String, Integer> skipListMap = new ConcurrentSkipListMap<>();
skipListMap.put("banana", 2);
skipListMap.put("apple", 1);
skipListMap.put("cherry", 3);
// Navigable operations are thread-safe
Map.Entry<String, Integer> firstEntry = skipListMap.firstEntry(); // apple=1
Map.Entry<String, Integer> higherEntry = skipListMap.higherEntry("banana"); // cherry=3
ConcurrentNavigableMap<String, Integer> headMap = skipListMap.headMap("banana", true); // apple, banana
// Concurrent sorted set
ConcurrentNavigableSet<String> skipListSet = new ConcurrentSkipListSet<>();
skipListSet.add("banana");
skipListSet.add("apple");
skipListSet.add("cherry");
// Navigable operations
String first = skipListSet.first(); // "apple"
String ceiling = skipListSet.ceiling("b"); // "banana"

Thread-safe queue implementations that support blocking operations for producer-consumer patterns.

A bounded blocking queue backed by an array.

// Fixed capacity of 10
BlockingQueue<String> arrayQueue = new ArrayBlockingQueue<>(10);
// Producer thread
new Thread(() -> {
try {
for (int i = 0; i < 20; i++) {
arrayQueue.put("Item " + i); // Blocks if queue is full
Thread.sleep(100);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
// Consumer thread
new Thread(() -> {
try {
while (true) {
String item = arrayQueue.take(); // Blocks if queue is empty
System.out.println("Consumed: " + item);
Thread.sleep(200); // Consuming slower than producing
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();

An optionally bounded blocking queue based on linked nodes.

Key Characteristics:

  • Can be bounded or unbounded (default constructor creates unbounded queue)
  • Typically higher throughput than ArrayBlockingQueue but less predictable performance
// Unbounded queue
BlockingQueue<Task> taskQueue = new LinkedBlockingQueue<>();
// Bounded queue with capacity 100
BlockingQueue<Task> boundedTaskQueue = new LinkedBlockingQueue<>(100);

An unbounded blocking queue that orders elements according to their natural ordering or a comparator.

// Natural ordering
BlockingQueue<Integer> priorityQueue = new PriorityBlockingQueue<>();
priorityQueue.put(5);
priorityQueue.put(2);
priorityQueue.put(8);
// Elements come out in priority order
Integer first = priorityQueue.take(); // 2
Integer second = priorityQueue.take(); // 5
// Custom comparator (process urgent tasks first)
BlockingQueue<Task> taskQueue = new PriorityBlockingQueue<>(11,
Comparator.comparing(Task::isUrgent).reversed()
.thenComparing(Task::getCreationTime));

A blocking queue of delayed elements, which are not available until their delay has expired.

class DelayedTask implements Delayed {
private final String name;
private final long executeAt;
public DelayedTask(String name, long delayMs) {
this.name = name;
this.executeAt = System.currentTimeMillis() + delayMs;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(executeAt - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS));
}
@Override
public String toString() {
return name;
}
}
// Queue of delayed tasks
DelayQueue<DelayedTask> delayQueue = new DelayQueue<>();
delayQueue.put(new DelayedTask("Task 1", 5000)); // Execute after 5 seconds
delayQueue.put(new DelayedTask("Task 2", 1000)); // Execute after 1 second
delayQueue.put(new DelayedTask("Task 3", 3000)); // Execute after 3 seconds
// Tasks will be available in order of expiration: Task 2, Task 3, Task 1
while (!delayQueue.isEmpty()) {
DelayedTask task = delayQueue.take(); // Blocks until a task's delay expires
System.out.println("Executing: " + task);
}

ConcurrentLinkedQueue and ConcurrentLinkedDeque

Section titled “ConcurrentLinkedQueue and ConcurrentLinkedDeque”

Unbounded thread-safe queue and deque implementations based on linked nodes.

Key Characteristics:

  • Non-blocking algorithm for high throughput
  • Weakly consistent iterators
  • No blocking operations (unlike BlockingQueue)
// Thread-safe queue
Queue<String> concurrentQueue = new ConcurrentLinkedQueue<>();
concurrentQueue.offer("item1");
concurrentQueue.offer("item2");
String item = concurrentQueue.poll(); // Retrieves and removes the head
// Thread-safe deque
Deque<String> concurrentDeque = new ConcurrentLinkedDeque<>();
concurrentDeque.offerFirst("first");
concurrentDeque.offerLast("last");
String first = concurrentDeque.pollFirst();
String last = concurrentDeque.pollLast();
Collection TypeImplementationUse When
MapConcurrentHashMapNeed thread-safe map with high concurrency
SortedMapConcurrentSkipListMapNeed thread-safe sorted map
ListCopyOnWriteArrayListRead-heavy, write-rare scenarios
SetCopyOnWriteArraySetRead-heavy, write-rare scenarios with unique elements
SortedSetConcurrentSkipListSetNeed thread-safe sorted set
QueueConcurrentLinkedQueueNeed high-throughput non-blocking queue
BlockingQueueArrayBlockingQueueNeed bounded blocking queue with predictable performance
BlockingQueueLinkedBlockingQueueNeed high-throughput blocking queue
BlockingQueuePriorityBlockingQueueNeed blocking queue with priority ordering
BlockingQueueDelayQueueNeed time-based scheduling queue
DequeConcurrentLinkedDequeNeed high-throughput non-blocking double-ended queue