CopyOnWriteArrayList 是 Java 中一个线程安全的 ArrayList 变体,它通过一种叫做“写时复制”(Copy-on-Write)的策略来实现线程安全。这种策略适用于读多写少的并发场景,因为每次修改操作都会导致整个底层数组的复制。下面是具体的实现方式和原理:
写时复制策略
基本原理:
- 每当我们需要修改 CopyOnWriteArrayList 中的内容(如添加、删除、设置元素等),CopyOnWriteArrayList 都不会直接在当前数组上进行修改。
- 相反,它会先将当前数组完整地复制一份,然后在这个新的数组副本上进行修改。
- 修改完成后,它会将内部的引用从旧数组更新到新修改过的数组。
- 因此,任何遍历操作都不会受到修改的影响,因为它们只是访问旧数组的引用,直到引用被更新。
线程安全:
- 这种写时复制机制确保了读取操作(如 get、iterator、listIterator 等)可以在不需要同步的情况下安全地执行,因为这些读取操作只访问不变的数组。
- 由于每次修改都涉及到完整数组的复制,写操作和读操作之间不会有冲突。
- 修改操作本身通过内部的 ReentrantLock (可重入锁)来保护,确保每次只有一个线程能执行写操作,从而保持操作的原子性。
示例
假设我们有一个 CopyOnWriteArrayList,初始内容为 [1, 2, 3]
。如果一个线程尝试添加元素 4
,而另一个线程同时迭代列表,情况如下:
-
添加元素:
- 线程 A 调用
add(4)
。 - CopyOnWriteArrayList 锁定,复制当前数组
[1, 2, 3]
。 - 在新数组
[1, 2, 3]
上添加4
,变为[1, 2, 3, 4]
。 - 更新内部数组引用指向
[1, 2, 3, 4]
。 - 解锁。
- 线程 A 调用
-
迭代元素:
- 线程 B 同时开始迭代列表。
- 由于写操作在复制的新数组上执行,迭代器仍然指向旧数组
[1, 2, 3]
,因此迭代过程中看不到变化。 - 迭代完成,得到元素
1, 2, 3
。
总结
CopyOnWriteArrayList 通过为每个写操作创建底层数组的新副本来避免读写冲突,从而提供了一种高效的机制来处理多线程环境中的读多写少场景。这种方式虽然在写操作时性能和内存使用上有所牺牲,但在需要高并发读且写操作较少的情况下,它提供了极好的线程安全性和迭代性能。
2024年6月29日 12:07 回复