AlgoMaster Logo

Concurrent Collections

17 min readUpdated December 16, 2025
Listen to this chapter
Unlock Audio

Concurrent Collections

A HashMap shared between threads. One thread reads while another writes. Suddenly, your application hangs in an infinite loop or throws a ConcurrentModificationException.

Standard collections are not designed for concurrent access. Using them in multithreaded code leads to data corruption, infinite loops, and crashes.

Concurrent collections solve this problem. They are designed from the ground up for safe, efficient access by multiple threads.

In this article, we'll explore:

  • Why standard collections fail in concurrent code
  • Different approaches to thread-safe collections
  • How concurrent collections work internally
  • When to use each type
  • Common patterns and pitfalls

If you're enjoying this newsletter and want to get even more value, consider becoming a [paid subscriber](https://blog.algomaster.io/subscribe).

As a paid subscriber, you'll unlock all premium articles and gain full access to all [premium courses](https://algomaster.io/newsletter/paid/resources) on [algomaster.io](https://algomaster.io).

Unlock Full Access

1. The Problem with Standard Collections

Standard collections like ArrayList, HashMap, and LinkedList are designed for single-threaded use. They have no synchronization, and their internal state can become corrupted when accessed by multiple threads.

What Goes Wrong

Running these concurrently can cause:

  • ConcurrentModificationException during iteration
  • Infinite loops (HashMap's internal linked list becomes circular)
  • Lost updates (entries disappear)
  • Corrupted data (wrong values returned)

HashMap Infinite Loop

In older implementations, concurrent modification of HashMap could cause the internal bucket chain to form a cycle, making get() loop forever.

2. Approaches to Thread-Safe Collections

There are three main approaches to making collections thread-safe:

Comparison

Scroll
Approach
Mechanism
Read Performance
Write Performance
Best For

Synchronized Wrappers

Single lock

Blocking

Blocking

Simple cases, low contention

Concurrent Collections

Segmented/lock-free

Non-blocking

Varies

High concurrency

Copy-on-Write

Copy on write

Non-blocking

Expensive

Read-heavy workloads

3. Synchronized Wrappers

The simplest approach is wrapping a standard collection with synchronization:

Every method acquires a lock on the wrapper object before delegating to the underlying collection.

Problems with Synchronized Wrappers

1. Compound operations are not atomic:

2. Iteration requires manual synchronization:

3. Poor scalability:

All operations use the same lock. Even read operations block each other.

4. ConcurrentHashMap

ConcurrentHashMap is the most commonly used concurrent collection. It provides thread-safe operations without locking the entire map.

How It Works

Instead of one lock for the entire map, ConcurrentHashMap uses fine-grained locking at the bucket level. Different threads can access different buckets concurrently.

Modern implementations use CAS operations and synchronized blocks only when necessary (during resizing or when buckets have many entries).

Key Features

Atomic Compound Operations

These operations solve the check-then-act problem:

Iteration

ConcurrentHashMap provides weakly consistent iterators. They:

  • Never throw ConcurrentModificationException
  • May or may not reflect modifications made after iterator creation
  • Are guaranteed to traverse elements as they existed at some point

5. CopyOnWriteArrayList

CopyOnWriteArrayList takes a different approach: every modification creates a new copy of the underlying array.

How It Works

  1. Reads see a snapshot of the array at the time they started
  2. Writes copy the entire array, modify the copy, then atomically swap the reference
  3. Ongoing reads continue using the old array

When to Use

Use CopyOnWriteArrayList
Avoid CopyOnWriteArrayList

Read-heavy workloads (99% reads)

Write-heavy workloads

Small lists

Large lists

Event listeners

Frequently modified data

Configuration that rarely changes

Queues or buffers

CopyOnWriteArraySet

A Set implementation backed by CopyOnWriteArrayList:

6. Blocking Queues

Blocking queues are designed for producer-consumer scenarios. They provide blocking operations that wait for the queue to become non-empty (for consumers) or non-full (for producers).

BlockingQueue Operations

Scroll
Operation
If Not Possible
Description

put(e)

Blocks

Insert, wait if full

take()

Blocks

Remove, wait if empty

offer(e)

Returns false

Insert if possible

poll()

Returns null

Remove if possible

offer(e, timeout)

Returns false after timeout

Insert with timeout

poll(timeout)

Returns null after timeout

Remove with timeout

Types of Blocking Queues

ArrayBlockingQueue

Fixed-size queue backed by an array:

LinkedBlockingQueue

Optionally bounded queue using linked nodes. Better throughput than ArrayBlockingQueue under high contention because it uses separate locks for put and take.

SynchronousQueue

A queue with zero capacity. Each put must wait for a take, and vice versa. It's a direct handoff mechanism.

7. ConcurrentLinkedQueue and ConcurrentLinkedDeque

Non-blocking, lock-free queues using CAS operations. They never block but may return null if empty.

When to Use

Use ConcurrentLinkedQueue
Use BlockingQueue

Non-blocking required

Need to wait for elements

Unbounded is acceptable

Need bounded capacity

Size not needed frequently

Back-pressure required

8. ConcurrentSkipListMap and ConcurrentSkipListSet

Thread-safe sorted collections based on skip lists. They provide O(log n) operations and maintain elements in sorted order.

Comparison with ConcurrentHashMap

Scroll
Aspect
ConcurrentHashMap
ConcurrentSkipListMap

Ordering

No

Sorted

Get/Put

O(1) average

O(log n)

Iteration order

Unpredictable

Sorted

Memory

Less

More (skip list pointers)

Range operations

No

Yes

9. Choosing the Right Collection

Quick Reference

Need
Use

Thread-safe HashMap

ConcurrentHashMap

Thread-safe sorted Map

ConcurrentSkipListMap

Thread-safe List (read-heavy)

CopyOnWriteArrayList

Producer-consumer queue

BlockingQueue (ArrayBlockingQueue, LinkedBlockingQueue)

Non-blocking queue

ConcurrentLinkedQueue

Thread-safe sorted Set

ConcurrentSkipListSet

Thread-safe Set (unsorted)

ConcurrentHashMap.newKeySet()

Direct handoff between threads

SynchronousQueue

Delayed task queue

DelayQueue

10. Common Patterns

Pattern 1: Caching with ConcurrentHashMap

Pattern 2: Producer-Consumer with BlockingQueue

Pattern 3: Event Listeners with CopyOnWriteArrayList

Pattern 4: Counting with ConcurrentHashMap

11. Common Pitfalls

Pitfall 1: Non-Atomic Check-Then-Act

Pitfall 2: Iterating Over Size

Pitfall 3: External Synchronization Defeating Purpose

Pitfall 4: Modifying Values Without Atomicity

Pitfall 5: Unbounded Queues Causing OOM

12. Performance Considerations

ConcurrentHashMap Tuning

Read vs Write Performance

Scroll
Collection
Read
Write
Iteration

ConcurrentHashMap

Fast

Fast

Weakly consistent

CopyOnWriteArrayList

Very fast

Very slow

Snapshot

ConcurrentSkipListMap

O(log n)

O(log n)

Sorted

BlockingQueue

Blocking

Blocking

Not recommended

Memory Overhead

13. Summary

Concurrent collections provide thread-safe access to shared data without the drawbacks of synchronized wrappers.

Key Takeaways

Collection
Use When

ConcurrentHashMap

General-purpose thread-safe map

CopyOnWriteArrayList

Read-heavy list with rare writes

BlockingQueue

Producer-consumer patterns

ConcurrentLinkedQueue

Non-blocking unbounded queue

ConcurrentSkipListMap/Set

Need sorted concurrent collection

Best Practices

  1. Prefer concurrent collections over synchronized wrappers
  2. Use atomic compound operations (putIfAbsent, computeIfAbsent, merge)
  3. Choose based on read/write ratio (CopyOnWrite for reads, ConcurrentHashMap for mixed)
  4. Bound your queues to prevent memory issues
  5. Avoid size() on unbounded queues (O(n) traversal)
  6. Don't wrap concurrent collections with external synchronization
  7. Use the right BlockingQueue for your use case

Concurrent collections are essential tools for building scalable multithreaded applications. Understanding their trade-offs helps you choose the right one for each situation.

Thank you for reading!

If you found it valuable, hit a like and consider subscribing for more such content every week.

If you have any questions or suggestions, leave a comment.

This post is public so feel free to share it.

Share

P.S. If you're enjoying this newsletter and want to get even more value, consider becoming a [paid subscriber](https://blog.algomaster.io/subscribe).

As a paid subscriber, you'll unlock all premium articles and gain full access to all [premium courses](https://algomaster.io/newsletter/paid/resources) on [algomaster.io](https://algomaster.io).

Unlock Full Access

There are [group discounts](https://blog.algomaster.io/subscribe?group=true), [gift options](https://blog.algomaster.io/subscribe?gift=true), and [referral bonuses](https://blog.algomaster.io/leaderboard) available.

Checkout my [Youtube channel](https://www.youtube.com/@ashishps_1/videos) for more in-depth content.

Follow me on [LinkedIn](https://www.linkedin.com/in/ashishps1/) and [X](https://twitter.com/ashishps_1) to stay updated.

Checkout my [GitHub repositories](https://github.com/ashishps1) for free interview preparation resources.

I hope you have a lovely day!

See you soon, Ashish

References

Images Needed

[IMAGE 1: Standard Collection Problems] <!-- Shows corruption, infinite loops, exceptions from concurrent access -->

[IMAGE 2: Synchronized vs Concurrent Collections] <!-- Visual comparison of locking strategies -->

[IMAGE 3: ConcurrentHashMap Segments] <!-- Shows bucket-level locking vs single lock -->

[IMAGE 4: Copy-on-Write Mechanism] <!-- Shows array copy on modification -->

[IMAGE 5: Producer-Consumer with BlockingQueue] <!-- Shows blocking put/take operations -->

[IMAGE 6: Collection Selection Flowchart] <!-- Decision tree for choosing the right collection -->