# 10. ArrayList和LinkedList在并发环境下存在哪些问题?如何解决?
# 标准答案
ArrayList 和 LinkedList 都是非线程安全的,在并发环境下可能会出现数据不一致、ConcurrentModificationException 等问题。解决方案包括使用 Collections.synchronizedList
、CopyOnWriteArrayList
或 ConcurrentLinkedDeque
等线程安全的集合。
# 答案解析
在多线程环境下,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();
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
}
2
3
4
5
6
这是因为 modCount
发生变化,导致 Iterator
在 next()
方法中检测到并抛出异常。
# 3. 读写锁竞争问题
LinkedList 在 remove()
时需要遍历整个链表,操作成本高;而 ArrayList 在 remove()
时需要移动元素。在并发环境下,多个线程可能竞争锁导致性能下降。
# 解决方案
# 1. Collections.synchronizedList
使用 Collections.synchronizedList()
可以在方法级别加锁:
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
但要注意,遍历时仍需要手动同步:
synchronized(syncList) {
for (Integer i : syncList) {
// 遍历
}
}
2
3
4
5
# 2. CopyOnWriteArrayList
如果写入少、读取多,CopyOnWriteArrayList
是更好的选择:
List<Integer> cowList = new CopyOnWriteArrayList<>();
cowList.add(1);
2
它在写入时复制整个底层数组,避免并发修改异常,但写入性能较低。
# 3. ConcurrentLinkedDeque
对于 LinkedList,可用 ConcurrentLinkedDeque
代替:
Deque<Integer> deque = new ConcurrentLinkedDeque<>();
deque.add(1);
2
它基于 CAS(Compare-And-Swap),无锁并发性能更好。
# 深入追问
CopyOnWriteArrayList
为什么适合读多写少的场景?- 为什么
ConcurrentLinkedDeque
适用于高并发场景,而LinkedList
不适用? synchronizedList
和CopyOnWriteArrayList
的性能对比如何?
# 相关面试题
CopyOnWriteArrayList
的底层实现是什么?ConcurrentLinkedDeque
如何保证线程安全?- 如何优化并发环境下的列表操作?