# 10. ArrayList和LinkedList在并发环境下存在哪些问题?如何解决?

# 标准答案

ArrayList 和 LinkedList 都是非线程安全的,在并发环境下可能会出现数据不一致、ConcurrentModificationException 等问题。解决方案包括使用 Collections.synchronizedListCopyOnWriteArrayListConcurrentLinkedDeque 等线程安全的集合。

# 答案解析

在多线程环境下,ArrayList 和 LinkedList 存在以下主要问题:

# 1. 竞态条件导致数据不一致

ArrayList 和 LinkedList 不是线程安全的,多线程同时修改时可能导致数据覆盖、丢失,甚至抛出异常。例如:

List<Integer> list = new ArrayList<>();
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
    executor.execute(() -> list.add(1));
}
executor.shutdown();
1
2
3
4
5
6

上面代码在多线程环境下,add() 操作不是原子的,可能会出现写入错误,导致数据不一致。

# 2. 迭代器的 fail-fast 机制

如果一个线程在遍历 ArrayList 或 LinkedList 时,另一个线程修改了集合,可能会触发 ConcurrentModificationException

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
for (Integer i : list) {
    list.remove(i); // 会抛出 ConcurrentModificationException
}
1
2
3
4
5
6

这是因为 modCount 发生变化,导致 Iteratornext() 方法中检测到并抛出异常。

# 3. 读写锁竞争问题

LinkedList 在 remove() 时需要遍历整个链表,操作成本高;而 ArrayList 在 remove() 时需要移动元素。在并发环境下,多个线程可能竞争锁导致性能下降。

# 解决方案

# 1. Collections.synchronizedList

使用 Collections.synchronizedList() 可以在方法级别加锁:

List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
1

但要注意,遍历时仍需要手动同步:

synchronized(syncList) {
    for (Integer i : syncList) {
        // 遍历
    }
}
1
2
3
4
5

# 2. CopyOnWriteArrayList

如果写入少、读取多,CopyOnWriteArrayList 是更好的选择:

List<Integer> cowList = new CopyOnWriteArrayList<>();
cowList.add(1);
1
2

它在写入时复制整个底层数组,避免并发修改异常,但写入性能较低。

# 3. ConcurrentLinkedDeque

对于 LinkedList,可用 ConcurrentLinkedDeque 代替:

Deque<Integer> deque = new ConcurrentLinkedDeque<>();
deque.add(1);
1
2

它基于 CAS(Compare-And-Swap),无锁并发性能更好。

# 深入追问

  • CopyOnWriteArrayList 为什么适合读多写少的场景?
  • 为什么 ConcurrentLinkedDeque 适用于高并发场景,而 LinkedList 不适用?
  • synchronizedListCopyOnWriteArrayList 的性能对比如何?

# 相关面试题

  • CopyOnWriteArrayList 的底层实现是什么?
  • ConcurrentLinkedDeque 如何保证线程安全?
  • 如何优化并发环境下的列表操作?