首页 > 解决方案 > 了解 CopyOnWriteArrayList 中的快照

问题描述

我在理解这个概念方面几乎没有问题。请纠正我哪里错了。

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

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

有人可以告诉我为什么在修改时需要制作副本,而迭代已经使用了自己的副本。为什么有 2 个应付,一个用于读取,一个用于写入?

我想我说的不正确,所以请你指出我错过了什么?

标签: javaconcurrencysnapshotcopy-on-write

解决方案


javadoc for说明CopyOnWriteArrayList如下:

“快照”风格的迭代器方法使用对创建迭代器时数组状态的引用。这个数组在迭代器的生命周期内永远不会改变,所以干扰是不可能的,并且迭代器保证不会抛出ConcurrentModificationException

您问:

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

...

我想我说的不正确,所以请你指出我错过了什么?

javadoc 没有说迭代器复制列表的状态,或者制作快照。它表示它使用对迭代器所在时间点的列表状态的引用。

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

这显然是你所缺少的。

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


后续问题。

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

因为如果每次修改列表时都CopyOnWriteArrayList 没有制作新的副本,那么现有的迭代器以所有定义不明确的方式看到修改。

其他选择是:

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

请注意,这CopyOnWriteArrayList是为列表阅读最多且很少修改的用例而设计的。对于这样的用例,在修改时复制是一个很好的策略。但如果列表需要大量修改,这是一个糟糕的策略……而且CopyOnWriteArrayList是错误的选择。


推荐阅读