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

<span>package</span> <span>org.example</span><span>;</span>
<span>import</span> <span>java.util.ArrayList</span><span>;</span>
<span>import</span> <span>java.util.List</span><span>;</span>
<span>public</span> <span>class</span> <span>ArrayListThreadSafetyDemo</span> <span>{</span>
<span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>throws</span> <span>InterruptedException</span> <span>{</span>
<span>List</span><span><</span><span>Integer</span><span>></span> <span>unsafeList</span> <span>=</span> <span>new</span> <span>ArrayList</span><span><>();</span>
<span>// Create two threads that add elements to the same list</span>
<span>Thread</span> <span>thread1</span> <span>=</span> <span>new</span> <span>Thread</span><span>(()</span> <span>-></span> <span>addElements</span><span>(</span><span>unsafeList</span><span>));</span>
<span>Thread</span> <span>thread2</span> <span>=</span> <span>new</span> <span>Thread</span><span>(()</span> <span>-></span> <span>addElements</span><span>(</span><span>unsafeList</span><span>));</span>
<span>thread1</span><span>.</span><span>start</span><span>();</span>
<span>thread2</span><span>.</span><span>start</span><span>();</span>
<span>// Wait for both threads to finish</span>
<span>thread1</span><span>.</span><span>join</span><span>();</span>
<span>thread2</span><span>.</span><span>join</span><span>();</span>
<span>// Expected size: 2000 (1000 elements per thread)</span>
<span>// The size is often less than 2000, and the program may occasionally throw exceptions like ArrayIndexOutOfBoundsException.</span>
<span>/* 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. */</span>
<span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Final list size: "</span> <span>+</span> <span>unsafeList</span><span>.</span><span>size</span><span>());</span>
<span>}</span>
<span>private</span> <span>static</span> <span>void</span> <span>addElements</span><span>(</span><span>List</span><span><</span><span>Integer</span><span>></span> <span>list</span><span>)</span> <span>{</span>
<span>for</span> <span>(</span><span>int</span> <span>i</span> <span>=</span> <span>0</span><span>;</span> <span>i</span> <span><</span> <span>1000</span><span>;</span> <span>i</span><span>++)</span> <span>{</span>
<span>list</span><span>.</span><span>add</span><span>(</span><span>i</span><span>);</span> <span>// Concurrent modification</span>
<span>}</span>
<span>}</span>
<span>}</span>
<span>package</span> <span>org.example</span><span>;</span>

<span>import</span> <span>java.util.ArrayList</span><span>;</span>
<span>import</span> <span>java.util.List</span><span>;</span>

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

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

        <span>thread1</span><span>.</span><span>start</span><span>();</span>
        <span>thread2</span><span>.</span><span>start</span><span>();</span>

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

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

        <span>/* 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. */</span>
        <span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Final list size: "</span> <span>+</span> <span>unsafeList</span><span>.</span><span>size</span><span>());</span>
    <span>}</span>

    <span>private</span> <span>static</span> <span>void</span> <span>addElements</span><span>(</span><span>List</span><span><</span><span>Integer</span><span>></span> <span>list</span><span>)</span> <span>{</span>
        <span>for</span> <span>(</span><span>int</span> <span>i</span> <span>=</span> <span>0</span><span>;</span> <span>i</span> <span><</span> <span>1000</span><span>;</span> <span>i</span><span>++)</span> <span>{</span>
            <span>list</span><span>.</span><span>add</span><span>(</span><span>i</span><span>);</span> <span>// Concurrent modification</span>
        <span>}</span>
    <span>}</span>
<span>}</span>
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.

<span>package</span> <span>org.example</span><span>;</span>
<span>import</span> <span>java.util.List</span><span>;</span>
<span>import</span> <span>java.util.concurrent.CopyOnWriteArrayList</span><span>;</span>
<span>public</span> <span>class</span> <span>CopyOnWriteArrayListThreadSafetyDemo</span> <span>{</span>
<span>public</span> <span>static</span> <span>void</span> <span>main</span><span>(</span><span>String</span><span>[]</span> <span>args</span><span>)</span> <span>throws</span> <span>InterruptedException</span> <span>{</span>
<span>List</span><span><</span><span>Integer</span><span>></span> <span>unsafeList</span> <span>=</span> <span>new</span> <span>CopyOnWriteArrayList</span><span><>();</span>
<span>// Create two threads that add elements to the same list</span>
<span>Thread</span> <span>thread1</span> <span>=</span> <span>new</span> <span>Thread</span><span>(()</span> <span>-></span> <span>addElements</span><span>(</span><span>unsafeList</span><span>));</span>
<span>Thread</span> <span>thread2</span> <span>=</span> <span>new</span> <span>Thread</span><span>(()</span> <span>-></span> <span>addElements</span><span>(</span><span>unsafeList</span><span>));</span>
<span>thread1</span><span>.</span><span>start</span><span>();</span>
<span>thread2</span><span>.</span><span>start</span><span>();</span>
<span>// Wait for both threads to finish</span>
<span>thread1</span><span>.</span><span>join</span><span>();</span>
<span>thread2</span><span>.</span><span>join</span><span>();</span>
<span>// Expected size: 2000 (1000 elements per thread)</span>
<span>System</span><span>.</span><span>out</span><span>.</span><span>println</span><span>(</span><span>"Final list size: "</span> <span>+</span> <span>unsafeList</span><span>.</span><span>size</span><span>());</span>
<span>}</span>
<span>private</span> <span>static</span> <span>void</span> <span>addElements</span><span>(</span><span>List</span><span><</span><span>Integer</span><span>></span> <span>list</span><span>)</span> <span>{</span>
<span>for</span> <span>(</span><span>int</span> <span>i</span> <span>=</span> <span>0</span><span>;</span> <span>i</span> <span><</span> <span>1000</span><span>;</span> <span>i</span><span>++)</span> <span>{</span>
<span>list</span><span>.</span><span>add</span><span>(</span><span>i</span><span>);</span> <span>// Concurrent modification</span>
<span>}</span>
<span>}</span>
<span>}</span>
<span>package</span> <span>org.example</span><span>;</span>

<span>import</span> <span>java.util.List</span><span>;</span>
<span>import</span> <span>java.util.concurrent.CopyOnWriteArrayList</span><span>;</span>

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

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

        <span>thread1</span><span>.</span><span>start</span><span>();</span>
        <span>thread2</span><span>.</span><span>start</span><span>();</span>

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

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

    <span>private</span> <span>static</span> <span>void</span> <span>addElements</span><span>(</span><span>List</span><span><</span><span>Integer</span><span>></span> <span>list</span><span>)</span> <span>{</span>
        <span>for</span> <span>(</span><span>int</span> <span>i</span> <span>=</span> <span>0</span><span>;</span> <span>i</span> <span><</span> <span>1000</span><span>;</span> <span>i</span><span>++)</span> <span>{</span>
            <span>list</span><span>.</span><span>add</span><span>(</span><span>i</span><span>);</span> <span>// Concurrent modification</span>
        <span>}</span>
    <span>}</span>
<span>}</span>
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.

<span>List</span><span><</span><span>Integer</span><span>></span> <span>safeList</span> <span>=</span> <span>Collections</span><span>.</span><span>synchronizedList</span><span>(</span><span>new</span> <span>ArrayList</span><span><>());</span>
<span>List</span><span><</span><span>Integer</span><span>></span> <span>safeList</span> <span>=</span> <span>Collections</span><span>.</span><span>synchronizedList</span><span>(</span><span>new</span> <span>ArrayList</span><span><>());</span>
List<Integer> safeList = Collections.synchronizedList(new ArrayList<>());

Enter fullscreen mode Exit fullscreen mode

原文链接:Choosing List Implementation in Java: ArrayList, LinkedList, CopyOnWriteArrayList, List.Of

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
If we believe that tomorrow will be better, we can bear a hardship today.
如果我们相信明天会更好,今天就能承受艰辛
评论 抢沙发

请登录后发表评论

    暂无评论内容