了解 CopyOnWriteArrayList 中的快照

Understanding snapshots in CopyOnWriteArrayList

我在理解这个概念方面几乎没有问题。不对的地方请指正

基本上,迭代会创建原始数组的快照(副本),因此修改集合的线程不会影响我们的迭代,因为迭代使用副本。所以这里没有 ConcurrentException,很好。

但后来我还了解到,任何修改都是通过制作原始集合的副本并使用该副本进行更改来完成的。然后将其设置为原来的。

谁能告诉我为什么在修改时需要制作一个副本,而迭代已经使用了自己的副本。为什么有2个副本,一个读,一个写?

我觉得我说的不对,所以请你指出我遗漏了什么?

CopyOnWriteArrayList 创建迭代器时,“快照”是对其当前数组的引用 - 而不是数组的副本。

迭代器可以使用对数组的引用,因为数组永远不会被修改。 CopyOnWriteArrayList 就像名字所说的那样:它会在任何更改时创建数组的新副本。如果一些迭代器同时使用旧数组,那不是问题,迭代器将继续使用旧数组,它不会看到对列表所做的任何修改。这使得迭代器“弱一致”。

CopyOnWriteArrayListjavadoc 表示如下:

The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException.

您问的是:

Basically, iteration makes a snapshot (copy) of original array, thus threads that modify collection won't affect our iteration because iteration uses a copy. So no ConcurrentException here, nice.

...

I think I said something incorrect, so please can you point what am I missing?

javadoc 没有说迭代器复制 列表的状态,或者制作快照。它说它使用 reference 指向列表在迭代器出现时的状态。

状态复制发生在列表发生变化时,如 javadoc 中其他地方所述。

这显然是你遗漏的。

提示:1) 务必仔细阅读 javadoc。 2) 如果您想确认 javadoc 所说的内容,请阅读源代码。但请记住,源代码可能包含不属于 API 规范的实现细节,并且可能会在不同的 Java 版本之间发生变化。


跟进问题。

Why is there need to make modifications on copy, when iterator uses snapshot and it doesn't see any modifications until it is finished?

因为如果 CopyOnWriteArrayList 没有 每次修改列表时都创建一个新副本,那么现有的迭代器 以所有 ill-defined 方式查看修改。

其他备选方案是:

  1. 在迭代列表时使用锁来阻止任何修改。这将是一个并发瓶颈。
  2. 每次开始迭代时创建快照。这也将成为并发瓶颈,因为您需要在创建快照时阻止修改。

请注意,CopyOnWriteArrayList 是为 use-cases 设计的,其中列表阅读最多,修改很少。对于这样的 use-cases,修改时复制是一个很好的策略。但是如果列表需要修改很多,这是一个糟糕的策略......并且 CopyOnWriteArrayList 是错误的选择。