Skip to main content

6.7 Concurrent & Thread-Safe Collections

← Back to Chapter 6: Multithreading & Concurrency

Standard collections in java.util (such as ArrayList, HashMap, and HashSet) are intentionally non-thread-safe to maximize single-threaded performance. Under concurrent access with at least one writer thread, standard collections exhibit data corruption, lost updates, infinite loops, and ConcurrentModificationException.


What are thread-safe collection?​

Suppose two threads are using the same collection:

Thread 1 → add data
Thread 2 → remove data
Thread 3 → read data

If the collection can handle these operations safely without corrupting data or producing inconsistent results, it is called thread-safe.

Thread safety is mainly about controlling concurrent access.

Thread-Safe collection and Corresponding normal collection​

Normal CollectionThread-Safe Alternative
ArrayListCopyOnWriteArrayList
HashMapConcurrentHashMap
HashSetCopyOnWriteArraySet
LinkedListConcurrentLinkedQueue / other concurrent collections depending on use case
TreeMapConcurrentSkipListMap
TreeSetConcurrentSkipListSet
ArrayDequeConcurrentLinkedDeque

There isn't always a direct one-to-one replacement. The thread-safe alternative depends on how you use the collection.

CopyOnWriteArrayList ("ArrayList")​

ArrayList is NOT thread-safe.

List<String> list = new ArrayList<>();

Because ArrayList does not automatically synchronize its operations.

CopyOnWriteArrayList is thread-safe.

List<String> list = new CopyOnWriteArrayList<>();

When somebody modifies the list, it creates a new internal copy instead of modifying the existing array directly.

When to use CopyOnWriteArrayList?​

READ → very frequent
WRITE → very rare

Because, Every modification creates a new copy, so it can be expensive when there are many writes.

ConcurrentHashMap ("Hashmap")​

HashMap is not thread-safe.

Map<Integer, String> map = new HashMap<>();

Because, Multiple threads can access the HashMap simultaneously without any built-in coordination. So you can get:

Race conditions
Inconsistent results
Problems during concurrent modification

ConcurrentHashMap is thread-safe.

Map<Integer, String> map = new ConcurrentHashMap<>();

It is specifically designed for concurrent access. It allows multiple threads to work with the map safely.

The important idea is:

Thread 1 → working with one part
Thread 2 → working with another part

They don't necessarily have to block each other for every operation.

Modern ConcurrentHashMap achieves this using highly optimized concurrency mechanisms such as CAS and fine-grained synchronization rather than simply putting one big lock around the entire map.

CopyOnWriteArraySet ("HashSet")​

HashSet is NOT thread-safe.

Set<String> set = new HashSet<>();

Because, Multiple threads modifying it simultaneously can cause inconsistent behavior.

CopyOnWriteArraySet is thread-safe.

Set<String> set = new CopyOnWriteArraySet<>();

Internally it uses a copy-on-write approach, similar to CopyOnWriteArrayList.

Best when:

Many reads
Few writes

ConcurrentSkipListMap ("TreeMap")​

TreeMap maintains sorted order, but it is not thread-safe.

Map<Integer, String> map = new TreeMap<>();

ConcurrentSkipListMap is thread-safe.

Map<Integer, String> map = new ConcurrentSkipListMap<>();

It provides:

thread safety
sorted keys
concurrent access

ConcurrentSkipListSet ("TreeSet")​

TreeSet provide Sorted elements nut is NOT Thread-safe.

Set<Integer> set = new TreeSet<>();

ConcurrentSkipListSet is Thread-safe

Set<Integer> set = new ConcurrentSkipListSet<>();

It Provides

Thread safety
Sorted order

ConcurrentLinkedQueue ("LinkedList")​

LinkedList can be used as a List or Deque but it is NOT Thread-safe

List<Integer> list = new LinkedList<>();

ConcurrentLinkedQueue is Thread-safe.

Queue<Integer> queue = new ConcurrentLinkedQueue<>();

Designed specifically for concurrent queue operations.

For example:

Thread 1 → producer → add()
Thread 2 → consumer → poll()

Multiple threads can safely use it.

It uses non-blocking/concurrent techniques rather than simply synchronizing every operation.

ConcurrentLinkedDeque ("ArrayDeque")​

ArrayDeque is Fast but it is NOT Thread-safe.

Deque<Integer> deque = new ArrayDeque<>();

ConcurrentLinkedDeque is Thread-Safe.

Deque<Integer> deque = new ConcurrentLinkedDeque<>();

Designed for:

Multiple threads
+
Deque operations

What about Vector?​

"Vector is a thread-safe version of ArrayList."
Vector<Integer> vector = new Vector<>();

Most of its individual methods are synchronized.

Problem with Vector​

Vector can be unnecessarily slow.

Because synchronization is built into many operations:

Thread 1 → lock
Thread 2 → WAIT
Thread 3 → WAIT

Even when threads don't really need to block each other.

For modern concurrent applications, CopyOnWriteArrayList or other concurrent collections are often a better fit depending on the workload.

Collections.synchronizedList()​

Another way of making a collection Thread-safe.

List<Integer> list =
Collections.synchronizedList(new ArrayList<>());

Now the underlying ArrayList is protected by synchronization. Similarly:

Collections.synchronizedMap(new HashMap<>());

Problem with Synchronized Collection​

Suppose:

List<Integer> list =
Collections.synchronizedList(new ArrayList<>());

Individual operations are synchronized.

But if you perform multiple operations as one logical action, you may still need external synchronization.

Therefore, we are using the respective Concurrent Collection rather they making any non Thread-safe collection synchronized