理解CopyOnWriteArrayList中的快照

huangapple go评论57阅读模式
英文:

Understanding snapshots in CopyOnWriteArrayList

问题

我在理解这个概念时遇到了一些问题。请在我理解错的地方纠正我。

基本上,迭代会对原始数组进行快照(复制),因此修改集合的线程不会影响我们的迭代,因为迭代使用的是副本。所以在这里不会出现ConcurrentException,很好。

但后来我也看到说,任何修改都是通过对原始集合进行复制并使用该副本进行更改。然后将其设置回原始集合。

有人可以告诉我为什么在修改时需要复制,当迭代已经使用了它自己的副本。为什么需要两个副本,一个用于读取,一个用于写入呢?

我想我说错了一些话,所以你能指出我漏掉了什么吗?

英文:

I have few issues in understanding this concept. Please correct me where I am wrong.

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.

But then I also read that any modification is done by making a copy of original collection and using that copy to make changes. Then it sets it to original one.

Can someone tell me why is there need to make a copy when modifying, when iteration already uses its own copy. Why have 2 copes, one for read, one for write?

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

答案1

得分: 1

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

迭代器可以使用对数组的引用,因为数组永远不会被修改。CopyOnWriteArrayList做的就是其名字所示:当更改任何内容时,它会制作数组的新副本。如果某个迭代器同时使用旧数组,那也不是问题,迭代器将继续使用旧数组,并且不会看到对列表所做的任何修改。这使得迭代器具有“弱一致性”。

英文:

When CopyOnWriteArrayList creates an iterator, the "snapshot" is a reference to its current array - not a copy of the array.

The iterator can use a reference to the array because the array is never modified. CopyOnWriteArrayList does that the name says: it makes a new copy of the array when anything is changed. If some iterator using the old array at the same time, that's not a problem, the iterator will just keep using the old array and it will not see any of the modifications made to the list. This makes the iterator "weakly consistent."

答案2

得分: 1

以下是翻译的内容:

javadoc中关于CopyOnWriteArrayList的说明如下:

> “快照”风格的迭代器方法在迭代器创建时使用数组状态的引用。在迭代器的生命周期内,此数组永远不会更改,因此不可能出现干扰,迭代器保证不会抛出ConcurrentModificationException

你提问道:

> 基本上,迭代会对原始数组进行快照(复制),因此修改集合的线程不会影响我们的迭代,因为迭代使用的是副本。因此这里不会有ConcurrentException,很好。
>
> ...
>
> 我想我说错了一些话,所以你能指出我错过了什么吗?

javadoc没有说迭代器会复制列表的状态,或者创建快照。它说的是在迭代器创建的那个时间点,它使用对列表状态的引用

状态的复制发生在列表发生变化时,正如javadoc中的其他地方所解释的那样。

这显然是你所遗漏的部分。

<sup>提示:1)始终仔细阅读javadoc。2)如果您想确认javadoc的内容,阅读源代码。但请记住,源代码可能包括不属于API规范的实现细节,并且在不同的Java版本之间可能会发生更改。</sup>


后续问题。

> 为什么需要在副本上进行修改,当迭代器使用快照并且在完成之前不会看到任何修改?

因为如果CopyOnWriteArrayList在每次列表被修改时创建一个新的副本,那么现有的迭代器将会以一种不明确的方式看到这些修改。

其他的替代方案包括:

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

请注意,CopyOnWriteArrayList被设计用于大部分时间进行读取,很少修改的用例。对于这种用例,修改时复制是一个很好的策略。但是,如果列表需要频繁修改,这将是一种不好的策略...并且CopyOnWriteArrayList就不是正确的选择。

英文:

The javadoc for CopyOnWriteArrayList says the following:

> 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.

You asked:

> 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?

The javadoc does not say that the iterator copies the state of the list, or makes a snapshot. It says that it uses a reference to the state of the list at the point in time that the iterator.

The state copying happens when the list is mutated, as explained elsewhere in the javadoc.

This is obviously what you were missing.

<sup>Hints: 1) Always read the javadoc carefully. 2) If you want to confirm what the javadoc says, read the source code. But bear in mind that the source code may include implementation details that are not part of the API spec, and may change between different Java versions.</sup>


Followup question.

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

Because if CopyOnWriteArrayList didn't make a fresh copy each time the list is modified, then existing iterators would see the modifications ... in all ill-defined way.

The other alternatives are:

  1. Use a lock to block any modifications while the list is being iterated. This would be a concurrency bottleneck.
  2. Create the snapshot each time you start an iteration. This would also be a concurrency bottleneck because you need to block modifications while the snapshot is created.

Note that CopyOnWriteArrayList is designed for use-cases where the list is most read and seldom modified. For such use-cases, copying on modify is a good strategy. But if the list needs to be modified a lot, this is a bad strategy ... and the CopyOnWriteArrayList is the wrong choice.

huangapple
  • 本文由 发表于 2020年8月25日 07:21:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/63569930.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定