Why Java’s ArrayList addAll Fails Under Concurrency and How to Fix It

This article explains the importance and challenges of concurrent programming, details Java's memory model and synchronization primitives, analyzes a real‑world case where concurrent addAll on ArrayList caused missing UI elements, and demonstrates how using thread‑safe collections resolves the issue.

Programmer DD
Programmer DD
Programmer DD
Why Java’s ArrayList addAll Fails Under Concurrency and How to Fix It

1. Significance and Challenges of Concurrent Programming

Concurrent programming aims to fully utilize each CPU core to achieve maximum performance, but hardware upgrades such as multi‑level caches and processor optimizations introduce cache‑coherency and instruction‑reordering problems.

Key challenges include managing mutable shared state, ensuring memory consistency, and handling processor optimizations that can break thread safety when multiple threads access the same object.

Two fundamental questions arise: how do threads communicate, and how are their executions synchronized?

2. Java Concurrent Programming

2.1 Java Memory Model

The Java Memory Model (JMM) balances high memory visibility with performance by allowing compilers and CPUs to reorder operations as long as program results remain unchanged. JMM defines how and when a thread can see updates to shared variables.

Local variables and method parameters are thread‑local and safe. For shared variables, JMM specifies visibility rules and synchronization requirements.

JMM defines the following rules for interaction between main memory and a thread’s working memory:

All variables reside in main memory.

Each thread has a private working memory that holds a copy of variables it reads or writes.

All variable operations occur in working memory; direct main‑memory access is prohibited.

Threads cannot directly access another thread’s working memory.

Eight primitive actions are defined:

lock – marks a variable in main memory as exclusively owned.

unlock – releases the exclusive mark.

read – transfers a variable’s value from main memory to working memory.

load – places the read value into the working‑memory copy.

use – passes a working‑memory value to the execution engine.

assign – writes a value from the execution engine into working memory.

store – moves a working‑memory value back to main memory.

write – writes the stored value into the main‑memory variable.

These actions must obey constraints such as pairing read with load and store with write, and performing unlock only after the variable has been synchronized to main memory.

2.2 Java Concurrency Keywords

Java provides volatile and synchronized to guarantee thread safety. volatile ensures atomic reads/writes of a single variable, while synchronized (implemented via monitorenter/monitorexit bytecode) provides mutual exclusion for a critical section, offering stronger guarantees.

2.3 Java Concurrent Containers and Utilities

2.3.1 CopyOnWriteArrayList

CopyOnWriteArrayList uses a re‑entrant lock for write operations, copying the entire array on each modification, which can be memory‑intensive.

public E get(int index) {
    return getArray()[index];
}
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();
    }
}

2.3.2 Collections.synchronizedList(new ArrayList<>())

This wrapper adds synchronized blocks around List operations; iteration still requires explicit synchronization.

public void add(int index, E element) {
    synchronized (mutex) { list.add(index, element); }
}
public E remove(int index) {
    synchronized (mutex) { return list.remove(index); }
}

2.3.3 ConcurrentLinkedQueue

Uses non‑blocking CAS loops to add nodes without locks.

public boolean offer(E e) {
    checkNotNull(e);
    final Node<E> newNode = new Node<>(e);
    for (Node<E> t = tail, p = t;;) {
        Node<E> q = p.next;
        if (q == null) {
            if (p.casNext(null, newNode)) {
                if (p != t) casTail(t, newNode);
                return true;
            }
        } else if (p == q) {
            p = (t != tail) ? tail : head;
        } else {
            p = (p != t && t != tail) ? tail : q;
        }
    }
}

3. Real‑World Case Study

3.1 Problem Discovery

During a COVID‑era traffic spike, a doctor‑side IM page occasionally displayed fewer buttons than expected, with no error logs and unreproducible in pre‑release.

3.2 Investigation Process

Analysis revealed that multiple threads added to a shared ArrayList via addAll, leading to lost elements due to non‑atomic size updates.

public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

Because size += numNew and the array copy are not atomic, concurrent threads could overwrite each other.

3.3 Solution

Replace the plain ArrayList with a thread‑safe list:

List<DoctorDiagImButtonInfoDTO> multiButtonList =
    Collections.synchronizedList(new ArrayList<>());

After deploying the synchronized list, the button loss issue disappeared.

3.4 Takeaways

When multiple threads operate on a shared collection, always use a thread‑safe implementation. Understanding JMM’s happens‑before principle and the role of volatile and CAS in concurrent utilities is essential.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javaconcurrencythread safetymultithreadingJMMArrayList
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

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.