乐闻世界logo
搜索文章和话题

CopyOnWriteArrayList如何实现线程安全?

7 个月前提问
6 个月前修改
浏览次数18

1个答案

1

CopyOnWriteArrayList 是 Java 中一个线程安全的 ArrayList 变体,它通过一种叫做“写时复制”(Copy-on-Write)的策略来实现线程安全。这种策略适用于读多写少的并发场景,因为每次修改操作都会导致整个底层数组的复制。下面是具体的实现方式和原理:

写时复制策略

基本原理

  • 每当我们需要修改 CopyOnWriteArrayList 中的内容(如添加、删除、设置元素等),CopyOnWriteArrayList 都不会直接在当前数组上进行修改。
  • 相反,它会先将当前数组完整地复制一份,然后在这个新的数组副本上进行修改。
  • 修改完成后,它会将内部的引用从旧数组更新到新修改过的数组。
  • 因此,任何遍历操作都不会受到修改的影响,因为它们只是访问旧数组的引用,直到引用被更新。

线程安全

  • 这种写时复制机制确保了读取操作(如 get、iterator、listIterator 等)可以在不需要同步的情况下安全地执行,因为这些读取操作只访问不变的数组。
  • 由于每次修改都涉及到完整数组的复制,写操作和读操作之间不会有冲突。
  • 修改操作本身通过内部的 ReentrantLock (可重入锁)来保护,确保每次只有一个线程能执行写操作,从而保持操作的原子性。

示例

假设我们有一个 CopyOnWriteArrayList,初始内容为 [1, 2, 3]。如果一个线程尝试添加元素 4 ,而另一个线程同时迭代列表,情况如下:

  1. 添加元素

    • 线程 A 调用 add(4)
    • CopyOnWriteArrayList 锁定,复制当前数组 [1, 2, 3]
    • 在新数组 [1, 2, 3] 上添加 4,变为 [1, 2, 3, 4]
    • 更新内部数组引用指向 [1, 2, 3, 4]
    • 解锁。
  2. 迭代元素

    • 线程 B 同时开始迭代列表。
    • 由于写操作在复制的新数组上执行,迭代器仍然指向旧数组 [1, 2, 3],因此迭代过程中看不到变化。
    • 迭代完成,得到元素 1, 2, 3

总结

CopyOnWriteArrayList 通过为每个写操作创建底层数组的新副本来避免读写冲突,从而提供了一种高效的机制来处理多线程环境中的读多写少场景。这种方式虽然在写操作时性能和内存使用上有所牺牲,但在需要高并发读且写操作较少的情况下,它提供了极好的线程安全性和迭代性能。

2024年6月29日 12:07 回复

你的答案