Concurrency

114. CopyOnWriteArrayList vs Collections.synchronizedList

CopyOnWriteArrayList optimizes lock-free reads by cloning the backing array on every write, while Collections.synchronizedList serializes all operations behind a single mutual-exclusion lock.

Java 21 (java.util.concurrent)

Explanation

When multiple threads read and write a shared List, developers must ensure thread safety. The JDK offers two standard solutions with drastically different performance characteristics:

1. Collections.synchronizedList(list): Wraps any standard list in a synchronization wrapper. Every method (get, add, remove, size) acquires a single shared intrinsic lock (this or mutex). Reads block other reads, creating severe lock contention under high concurrency. Furthermore, iterating over a synchronized list still requires manual synchronization on the list to avoid ConcurrentModificationException. 2. CopyOnWriteArrayList: All read operations (get, iterator, contains) are 100% lock-free and execute directly on the current volatile array reference without blocking. Whenever a mutation occurs (add, set, remove), the entire backing array is cloned, modified, and swapped atomically. - Iterators create a snapshot of the array at creation time and never throw ConcurrentModificationException, but they do not reflect subsequent writes and do not support iterator.remove(). - Ideal use case: Read-heavy, write-rare structures like event listener registries or whitelist configurations. - Trap: If writes are frequent or lists are large, the array copying causes severe memory churn and GC pressure.

Example

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> listeners = new CopyOnWriteArrayList<>();
        listeners.add("OnLogin");
        listeners.add("OnLogout");

        // Safe iteration during concurrent modification:
        Iterator<String> iterator = listeners.iterator();

        // Mutate list while iterator is active!
        listeners.add("OnPasswordReset");

        System.out.println("Snapshot iterator output (does not see OnPasswordReset):");
        while (iterator.hasNext()) {
            System.out.println("  Listener: " + iterator.next());
        }

        System.out.println("Current list contents:");
        System.out.println("  " + listeners);
    }
}

Sources