hello啊,各位观众姥爷们!!!本baby今天来报道了!哈哈哈哈哈嗝🐶
面试官:详细说说CopyOnWriteArrayList
CopyOnWriteArrayList 详解
CopyOnWriteArrayList 是 Java 并发包(java.util.concurrent
)中提供的线程安全列表,基于“写时复制”(Copy-On-Write)机制实现。它适用于读多写少的高并发场景,如事件监听器列表、配置管理等。
核心特性
特性 | 说明 |
---|---|
线程安全 | 读操作无锁,写操作通过锁保证线程安全。 |
数据一致性 | 读操作基于快照,迭代期间不会抛出 ConcurrentModificationException 。 |
写开销 | 每次修改操作会复制底层数组,内存占用较高。 |
适用场景 | 读操作频繁,写操作极少(如监听器管理、配置存储)。 |
底层实现
-
数据结构
- 基于动态数组(
volatile Object[] array
)存储数据。 - 所有读操作直接访问当前数组,无需同步。
- 基于动态数组(
-
写时复制(COW)
- 修改操作流程:
- 加锁(
ReentrantLock
)。 - 复制原数组,生成新数组。
- 在新数组上执行修改操作。
- 将底层数组引用指向新数组(
setArray(newArray)
)。 - 释放锁。
- 加锁(
- 代码示例(
add
方法):public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len + 1);newElements[len] = e;setArray(newElements);return true;} finally {lock.unlock();} }
- 修改操作流程:
-
迭代器
- 基于迭代器创建时的数组快照遍历数据。
- 不支持修改操作(如
remove
、set
),调用会抛出UnsupportedOperationException
。 - 代码示例:
public Iterator<E> iterator() {return new COWIterator<E>(getArray(), 0); }
优点与缺点
优点 | 缺点 |
---|---|
读操作无锁,性能极高。 | 写操作内存开销大(复制全量数据)。 |
避免并发修改异常(ConcurrentModificationException )。 | 数据弱一致性(读操作可能不反映最新状态)。 |
实现简单,适合读多写少场景。 | 写操作频繁时性能急剧下降。 |
适用场景
-
监听器列表:
如 GUI 事件监听器,注册后极少修改,但频繁触发事件(读)。// 添加监听器(写操作少) listeners.add(new Listener());// 触发事件(读操作多) for (Listener listener : listeners) {listener.onEvent(); }
-
配置管理:
系统配置通常加载后很少修改,但频繁读取。CopyOnWriteArrayList<Config> configs = loadConfigs(); // 读取配置(无锁) String value = configs.get(0).getProperty("key");
-
缓存快照:
需要缓存某个时间点的数据快照供查询。
性能对比
操作 | ArrayList | CopyOnWriteArrayList | Collections.synchronizedList |
---|---|---|---|
读(单线程) | O(1)(最快) | O(1)(无锁,快) | O(1)(同步开销,较慢) |
读(高并发) | 非线程安全,需外部同步 | O(1)(无锁,最快) | O(1)(同步开销,慢) |
写(单线程) | O(1)(快) | O(n)(复制数组,慢) | O(1)(同步开销,较慢) |
写(高并发) | 非线程安全 | O(n)(锁竞争,最慢) | O(1)(同步开销,慢) |
注意事项
-
避免频繁写操作:
批量写入时,优先使用addAll
代替多次add
,减少数组复制次数。// 不推荐 for (String item : items) {list.add(item); }// 推荐 list.addAll(items);
-
迭代器弱一致性:
迭代器遍历的是创建时的快照,可能无法感知后续修改。CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(Arrays.asList(1, 2, 3)); Iterator<Integer> it = list.iterator(); list.add(4); while (it.hasNext()) {System.out.print(it.next()); // 输出 1,2,3(不包含4) }
-
内存监控:
大对象或超大数组可能导致内存压力,需监控堆内存使用。
🐮🐎
- 使用场景:读多写少,允许数据弱一致性。
- 替代方案:
- 写多读少:考虑
ConcurrentLinkedQueue
或ConcurrentHashMap
。 - 强一致性需求:使用锁或
synchronizedList
。
- 写多读少:考虑
- 最佳实践:结合业务特点选择数据结构,必要时进行性能压测。