1. 概述
本文将深入探讨 java.util.concurrent
包中的 CopyOnWriteArrayList。这个并发集合类在多线程编程中非常实用,特别适合需要线程安全遍历列表且无需显式同步的场景。
2. CopyOnWriteArrayList API
CopyOnWriteArrayList 的设计采用了巧妙的"写时复制"机制实现线程安全:
- ✅ 核心原理:调用
add()
或remove()
等修改方法时,会创建底层数组的全新副本 - ✅ 遍历安全:迭代操作基于创建迭代器时的不可变快照,即使其他线程并发修改也不受影响
- ⚠️ 性能权衡:修改操作需复制整个数组,适合读多写少的场景。频繁修改会导致性能下降
// 创建迭代器时获取数据快照
Iterator<String> iterator = list.iterator();
// 后续修改不影响已创建的迭代器
list.add("new element");
3. 在插入时迭代 CopyOnWriteArrayList
通过示例理解其行为:
CopyOnWriteArrayList<Integer> numbers
= new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8});
// 创建迭代器(获取当前快照)
Iterator<Integer> iterator = numbers.iterator();
// 添加新元素(创建新数组副本)
numbers.add(10);
// 迭代快照中不包含新元素
List<Integer> result = new LinkedList<>();
iterator.forEachRemaining(result::add);
assertThat(result).containsOnly(1, 3, 5, 8); // ✅ 不包含10
// 新迭代器包含所有元素
Iterator<Integer> iterator2 = numbers.iterator();
List<Integer> result2 = new LinkedList<>();
iterator2.forEachRemaining(result2::add);
assertThat(result2).containsOnly(1, 3, 5, 8, 10); // ✅ 包含10
关键点:
- 迭代器基于创建时刻的数据快照
- 后续修改不影响已存在的迭代器
- 新迭代器反映最新数据状态
4. 迭代时不允许删除
CopyOnWriteArrayList 的迭代器是只读的,尝试删除会抛出异常:
@Test(expected = UnsupportedOperationException.class)
public void whenIterateOverItAndTryToRemoveElement_thenShouldThrowException() {
CopyOnWriteArrayList<Integer> numbers
= new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8});
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext()) {
iterator.remove(); // ❌ 抛出 UnsupportedOperationException
}
}
⚠️ 踩坑提醒:不要在迭代时尝试删除元素,这是设计限制而非 bug
5. 总结
CopyOnWriteArrayList 的核心价值在于:
- ✅ 线程安全遍历:无需同步即可安全迭代
- ✅ 弱一致性:迭代器反映创建时的数据状态
- ❌ 写操作昂贵:每次修改都复制整个数组
适用场景:
- 事件监听器管理
- 配置信息存储
- 读操作远多于写操作的场景
完整示例代码可在 GitHub 项目 中获取(Maven 项目,可直接导入运行)。