Java
JDK
Concurrent Programming
Data Structures
List Interface

Is there a concurrent List in Java's JDK?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

The JDK does not provide a type literally named ConcurrentList, but it does provide several ways to make list-like access safe when multiple threads are involved. The correct choice depends less on the word "list" and more on the ratio of reads to writes, how you iterate, and whether order actually matters.

What the JDK Actually Offers

If you need a List that can be shared across threads, the two standard options are Collections.synchronizedList(...) and CopyOnWriteArrayList. They solve different problems.

Collections.synchronizedList(...) wraps an existing list and places a monitor around each individual operation. That means calls such as add, get, and remove are serialized. It is simple and often good enough for low to moderate contention.

java
1import java.util.ArrayList;
2import java.util.Collections;
3import java.util.List;
4
5public class SynchronizedListDemo {
6    public static void main(String[] args) {
7        List<String> names = Collections.synchronizedList(new ArrayList<>());
8        names.add("Ada");
9        names.add("Linus");
10
11        synchronized (names) {
12            for (String name : names) {
13                System.out.println(name);
14            }
15        }
16    }
17}

The explicit synchronized (names) block around iteration is important. The wrapper makes single operations safe, but iteration is a multi-step activity. Without external synchronization, another thread may modify the list while you traverse it.

When CopyOnWriteArrayList Fits Better

CopyOnWriteArrayList takes the opposite tradeoff. Reads are cheap and iterators are stable because every write creates a new backing array. That sounds expensive, and it is, but only when writes are frequent. If the list is mostly read and only occasionally updated, this class is usually the cleanest option.

java
1import java.util.concurrent.CopyOnWriteArrayList;
2
3public class CopyOnWriteDemo {
4    public static void main(String[] args) {
5        CopyOnWriteArrayList<String> listeners = new CopyOnWriteArrayList<>();
6        listeners.add("audit");
7        listeners.add("metrics");
8
9        for (String listener : listeners) {
10            System.out.println("Dispatching to " + listener);
11            listeners.add("late-registration");
12        }
13
14        System.out.println(listeners);
15    }
16}

This loop does not throw ConcurrentModificationException because the iterator sees a snapshot. The new element appears in the list afterward, but not in the current traversal.

That behavior makes CopyOnWriteArrayList useful for listener registries, configuration snapshots, and routing tables that are read constantly and changed rarely.

Picking the Right Tool

A good rule is to start from access patterns.

Use Collections.synchronizedList(...) when writes are common, the list is not extremely hot, and you can control the locking discipline around compound actions.

Use CopyOnWriteArrayList when reads dominate and iteration must stay simple and safe even while other threads update the list.

If neither fits, the real answer may be that you do not need a List at all. Queues, maps, and sets have stronger concurrent support in java.util.concurrent. For example, producer-consumer pipelines usually want BlockingQueue, not a synchronized ArrayList.

Compound Operations Need Extra Thought

The biggest source of bugs is assuming that a thread-safe collection makes every sequence of operations atomic. It does not.

Consider this code:

java
if (!names.contains("Grace")) {
    names.add("Grace");
}

Even with a synchronized wrapper, two threads can both observe that the element is absent and both add it. If you need a guarantee around the whole sequence, guard the entire check-and-act block with the same lock.

java
1synchronized (names) {
2    if (!names.contains("Grace")) {
3        names.add("Grace");
4    }
5}

With CopyOnWriteArrayList, the same logical race exists. The class protects internal consistency, but it does not make your higher-level business rule atomic.

Performance Tradeoffs in Practice

Collections.synchronizedList(...) adds contention because all callers compete for one lock. Under heavy parallel access, that can become a bottleneck.

CopyOnWriteArrayList avoids lock-heavy iteration, but every mutation copies the whole array. If the list is large or modified often, memory churn and CPU overhead become obvious.

That is why there is no universal concurrent list in the JDK. Different workloads need different failure modes and performance characteristics.

Common Pitfalls

  • Assuming the JDK has a ConcurrentList type. It does not; you choose from wrappers or specialized implementations.
  • Iterating over Collections.synchronizedList(...) without synchronizing on the list. That leaves traversal exposed to concurrent changes.
  • Using CopyOnWriteArrayList for write-heavy workloads. The full-array copy on each mutation becomes expensive quickly.
  • Treating thread-safe methods as if they made compound business operations atomic. Wrap check-and-act sequences explicitly.
  • Using a list when a queue, map, or set matches the workload better. The wrong abstraction creates avoidable contention.

Summary

  • The JDK does not include a dedicated ConcurrentList interface or class.
  • 'Collections.synchronizedList(...) is appropriate when you want a normal list with explicit locking.'
  • 'CopyOnWriteArrayList is best when reads and iteration dominate over writes.'
  • Safe single operations do not automatically make multi-step logic atomic.
  • Concurrent design starts with access patterns, not with class names.

Course illustration
Course illustration

All Rights Reserved.