Backend Development 11 min read

Why Use Read‑Write Locks When Java Already Offers Thread‑Safe Collections?

This article examines the internal workings of Java's CopyOnWriteArrayList, explains why its weak consistency still necessitates read‑write locks in read‑heavy scenarios, and compares exclusive locks with read‑write locks to achieve strong data consistency without sacrificing performance.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
Why Use Read‑Write Locks When Java Already Offers Thread‑Safe Collections?

In Java, many collections are thread‑safe, but they often provide only weak consistency. This article uses the widely used CopyOnWriteArrayList to illustrate the value of read‑write locks.

CopyOnWriteArrayList Core Source Analysis

The class maintains an exclusive lock and an internal volatile array:

<code>final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;</code>

All mutating operations acquire this lock, copy the underlying array, modify the copy, and then replace the original array.

add Method

<code>public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}</code>

The method locks, copies the array, adds the element, and swaps the reference.

remove Method

<code>public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index, numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}</code>

It also locks, creates a smaller array, copies the remaining elements, and swaps.

set Method

<code>public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);
        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // Not quite a no‑op; ensures volatile write semantics
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}</code>

Like add and remove, it locks, copies, modifies, and swaps.

size Method

<code>public int size() {
    return getArray().length;
}</code>

Reading the size simply returns the current array length without locking.

Iterator Construction

<code>public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}</code>

The iterator works on a snapshot of the array, so no lock is needed for iteration.

Overall, CopyOnWriteArrayList follows a write‑copy‑replace strategy: every write acquires an exclusive lock, copies the array, performs the modification on the copy, and then atomically swaps the reference. Reads operate on the snapshot without locking, which yields high read concurrency but weak consistency because a read may see stale data.

Applicable Scenarios

The structure excels in read‑many, write‑few situations. Writes are serialized and involve array copying, which hurts performance under heavy write load, while reads can proceed concurrently with minimal overhead.

Limitations and Consistency Issues

Because writes replace the whole array, the collection provides only weak consistency . A thread reading the size may not see an element that another thread has just added but not yet swapped in, leading to stale reads.

Ensuring Strong Consistency

Strong consistency can be achieved by protecting both reads and writes with the same exclusive lock, but this degrades read performance. A better compromise is to use a read‑write lock (e.g., ReentrantReadWriteLock ): writes acquire the write lock (exclusive), while reads acquire the read lock (shared). This guarantees that a read sees the latest committed state while still allowing multiple concurrent reads.

Java provides ReentrantReadWriteLock based on AQS; developers can explore its source for details.

Conclusion

Even with thread‑safe collections like CopyOnWriteArrayList , read‑write locks are valuable when strong data consistency is required. Choosing between concurrent collections, exclusive locks, or read‑write locks depends on the specific read‑write ratio and consistency needs of the application.

JavaconcurrencyThread Safetyreadwritelockcopyonwritearraylist
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.