Concurrent Containers in Java: A Detailed Explanation of CopyOnWriteArrayList
1. Container Overview and Problem Background
In a multi-threaded environment, concurrent read and write operations on traditional collection classes (such as ArrayList) can lead to data inconsistency or ConcurrentModificationException. For example, if one thread is iterating over a list while another thread simultaneously modifies the list's structure (adding or deleting elements), the iteration will fail. CopyOnWriteArrayList is a thread-safe list provided by the JUC package, specifically designed to solve such problems.
2. Core Design Philosophy: Copy-On-Write
- Basic Logic: When a modification (add, delete, or update operation) is needed, instead of directly modifying the original array, it first creates a copy of the current array, performs the modification on the copy, and then points the original array reference to the new array after completion.
- Read-Write Separation: Read operations (such as get and iteration) are always performed on the original array without any locking; write operations ensure thread safety by copying a new array. This design makes read operations extremely efficient, suitable for read-heavy, write-light scenarios.
3. Internal Implementation Mechanism
public class CopyOnWriteArrayList<E> {
private transient volatile Object[] array; // volatile ensures visibility
// Read operation: directly returns the element, no lock
public E get(int index) {
return (E) array[index];
}
// Write operation: locks and copies the new array
public boolean add(E e) {
synchronized (lock) { // Uses a reentrant lock to ensure atomicity
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); // Copy the new array
newElements[len] = e;
setArray(newElements); // Replace the original array reference
return true;
}
}
}
4. Detailed Explanation of Key Features
- Eventual Consistency: Read operations may not immediately see modifications made by other threads, but they are guaranteed to eventually see the write results (through the happens-before rule of volatile).
- Weakly Consistent Iterators: Iterators save a snapshot of the current array when created. Traversal will not reflect modifications made by other threads during iteration but will not throw ConcurrentModificationException.
- Memory Overhead: Each write operation requires copying the entire array, leading to significant memory usage during frequent modifications and potentially triggering GC.
5. Applicable Scenarios and Limitations
- Applicable Scenarios:
- Read operations far outnumber write operations (e.g., listener lists, configuration information caches).
- The collection size is small, and write operations are infrequent.
- Non-Applicable Scenarios:
- Scenarios with frequent write operations or large datasets (high copying cost).
- Scenarios requiring real-time access to the latest data (read operations may access stale data).
6. Comparison with Synchronized Containers
- Vector/SynchronizedList: Synchronizes all methods via synchronized, resulting in poor concurrent performance for both reads and writes.
- CopyOnWriteArrayList: Read operations are completely lock-free, and write operations avoid blocking read operations by copying, significantly outperforming synchronized containers in read-heavy, write-light scenarios.
7. Practical Example
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// Thread 1: Looping reads (not blocked by write operations)
new Thread(() -> {
for (String s : list) { // Iterator based on the initial snapshot
System.out.println(s);
}
}).start();
// Thread 2: Adding elements
new Thread(() -> {
list.add("new element"); // Write operation copies a new array
}).start();
Summary: CopyOnWriteArrayList adopts a copy-on-write strategy, trading space for time to achieve high concurrency for read operations. When using it, its read-write characteristics and memory overhead must be carefully weighed to ensure they align with business scenario requirements.