ArrayList是否是线程安全的?

参考回答

ArrayList 不是线程安全的。

ArrayList 是 Java 中一个常用的动态数组实现,位于 java.util 包中。它的操作(如添加、删除、遍历)没有加锁或同步,因此在多线程环境下同时访问或修改 ArrayList 可能导致数据不一致或抛出异常(如 ConcurrentModificationException)。


详细讲解与拓展

1. 为什么 ArrayList 不是线程安全的?

ArrayList 的内部实现没有对操作进行同步处理。例如,add()remove()get() 等方法都没有使用 synchronized 关键字或其他同步机制。

示例问题:
import java.util.ArrayList;

public class ArrayListThreadExample {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();

        // 创建两个线程同时操作 ArrayList
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1000; i < 2000; i++) {
                list.add(i);
            }
        });

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

        // 等待线程完成
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打印结果
        System.out.println("List size: " + list.size());
    }
}
可能出现的问题:
  1. 数据不一致:由于线程争用,可能导致添加的元素数量与预期不符。
  2. 并发修改异常:如果一个线程在遍历 ArrayList 时,另一个线程对其进行修改,可能抛出 ConcurrentModificationException

2. 如何使 ArrayList 线程安全?

方案 1:使用 Collections.synchronizedList 包装

可以使用 Collections.synchronizedList 将一个普通的 ArrayList 包装成线程安全的版本。

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

public class SynchronizedListExample {
    public static void main(String[] args) {
        List<Integer> list = Collections.synchronizedList(new ArrayList<>());

        // 多线程操作
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1000; i < 2000; i++) {
                list.add(i);
            }
        });

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

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("List size: " + list.size());
    }
}

注意

  • Collections.synchronizedList
    

    的方法本身是线程安全的,但在迭代时仍需要手动同步:

    synchronized (list) {
      for (Integer value : list) {
          System.out.println(value);
      }
    }
    
方案 2:使用 CopyOnWriteArrayList

CopyOnWriteArrayList 是一个线程安全的集合类,适合读多写少的场景。

代码示例

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                list.add(i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 1000; i < 2000; i++) {
                list.add(i);
            }
        });

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

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("List size: " + list.size());
    }
}

特点

  • 每次写操作都会创建一个新的底层数组,因此适合读操作多、写操作少的场景。
  • 避免了手动同步的麻烦。

3. 性能对比

特性 ArrayList Collections.synchronizedList CopyOnWriteArrayList
线程安全性
读操作性能 中等
写操作性能 中等
适用场景 单线程操作或无并发环境 多线程写操作较多 多线程读多写少的场景

总结

  • ArrayList 不是线程安全的,在多线程环境下可能导致数据不一致或抛出异常。
  • 如果需要线程安全的列表:
    1. 可以使用 Collections.synchronizedListArrayList 进行包装。
    2. 或者直接使用 CopyOnWriteArrayList,它在多线程环境下性能更优,适合读多写少的场景。

发表评论

后才能评论