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
}
}
}
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
}
}
}
You can also use to have a thread-safe list.
List<Integer> safeList = Collections.synchronizedList(new ArrayList<>());
Top comments (0)