DEV Community

Luqman Rom
Luqman Rom

Posted on

Choosing List Implementation in Java: ArrayList, LinkedList, CopyOnWriteArrayList, List.Of

To choose which list you need you to use requires some basic understanding of each of the implementations.

Summary

Implementation Underlying Structure Thread-Safe? Remarks
ArrayList Dynamic Array No General-purpose, random access.
LinkedList Doubly Linked List No Frequent insertions/deletions. Slower access (O(n)).
CopyOnWriteArrayList Dynamic Array Yes Thread-safe, read-heavy workloads.
List.of() / Immutable Fixed Array Yes Unmodifiable data.

When to Use Which?

  • ArrayList: Default choice for most cases.
  • LinkedList: Frequent insertions/deletions at both ends (e.g., queues).
  • CopyOnWriteArrayList: Thread-safe reads with infrequent writes.
  • Immutable Lists (List.of()): Data that never changes.

Thread-Safe Demo

Here is examples to see how ArrayList is not thread safe

package org.example;

import java.util.ArrayList;
import java.util.List;

public class ArrayListThreadSafetyDemo {
    public static void main(String[] args) throws InterruptedException {
        List<Integer> unsafeList = new ArrayList<>();

        // Create two threads that add elements to the same list
        Thread thread1 = new Thread(() -> addElements(unsafeList));
        Thread thread2 = new Thread(() -> addElements(unsafeList));

        thread1.start();
        thread2.start();

        // Wait for both threads to finish
        thread1.join();
        thread2.join();

        // Expected size: 2000 (1000 elements per thread)
        // The size is often less than 2000, and the program may occasionally throw exceptions like ArrayIndexOutOfBoundsException.

        /*
            Race Condition: Two threads might both try to increment the size field and write to the same array index, causing one write to be overwritten.
            Internal Array Corruption: The internal elementData array might resize inconsistently, leading to index errors.
            Lost Updates: The size counter may not update atomically, resulting in fewer elements than expected.
         */
        System.out.println("Final list size: " + unsafeList.size());
    }

    private static void addElements(List<Integer> list) {
        for (int i = 0; i < 1000; i++) {
            list.add(i); // Concurrent modification
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

To have a thread safe list, choose CopyOnWriteArrayList instead. Since it is coming from same List interface, most methods are the same.

package org.example;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListThreadSafetyDemo {
    public static void main(String[] args) throws InterruptedException {
        List<Integer> unsafeList = new CopyOnWriteArrayList<>();

        // Create two threads that add elements to the same list
        Thread thread1 = new Thread(() -> addElements(unsafeList));
        Thread thread2 = new Thread(() -> addElements(unsafeList));

        thread1.start();
        thread2.start();

        // Wait for both threads to finish
        thread1.join();
        thread2.join();

        // Expected size: 2000 (1000 elements per thread)
        System.out.println("Final list size: " + unsafeList.size());
    }

    private static void addElements(List<Integer> list) {
        for (int i = 0; i < 1000; i++) {
            list.add(i); // Concurrent modification
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

You can also use to have a thread-safe list.

List<Integer> safeList = Collections.synchronizedList(new ArrayList<>());
Enter fullscreen mode Exit fullscreen mode

Top comments (0)