繁体   English   中英

为什么 CopyOnWriteArrayList 需要用于写入和读取操作的副本?

[英]Why CopyOnWriteArrayList needs copies for both write and read operations?

来自这篇文章,它说:

当我们使用任何修改方法时——例如 add() 或 remove()——CopyOnWriteArrayList 的全部内容被复制到新的内部副本中。

由于这个简单的事实,我们可以以安全的方式迭代列表,即使发生并发修改。

当我们在 CopyOnWriteArrayList 上调用 iterator() 方法时,我们会返回一个由 CopyOnWriteArrayList 内容的不可变快照备份的迭代器。

它的内容是从创建迭代器时开始在 ArrayList 内的数据的精确副本。 即使同时其他线程从列表中添加或删除元素,该修改也会制作数据的新副本,该副本将用于从该列表中进行任何进一步的数据查找。

接下来要问自己的一个简单问题是为什么两者兼而有之? 基本上,据我了解,写操作是在新副本上进行的,而读操作是在集合的克隆上进行的。

例如,如果在新副本上完成写入,这意味着我可以迭代“原始”集合——这意味着它不会受到影响。 那么为什么要在另一个副本(快照)中增加存储元素的开销呢? 或者相反的方向,如果我将元素存储在副本(快照)中,为什么需要在副本上完成写入,当我从字面上迭代克隆而不是“原始”集合时(意味着快照永远不会改变)?

我希望这个问题是合法的,因为我确实检查了互联网上所有可能的来源,但没有一篇文章帮助我消除这种困惑。 我在这里想念什么?

当您调用iterator时, CopyOnWriteArrayList不会创建数组的副本,正如文档所说:

“快照”样式的迭代器方法在创建迭代器时使用对数组的 state 的引用

请注意“参考”一词。

这句话的措辞相当糟糕:

它的内容是从创建迭代器时开始在 ArrayList 内的数据的精确副本。

这并不意味着调用iterator()时会创建数组的副本。 它应该说:

其内容与创建迭代器时 ArrayList 中的数据相同

该段更重要的一点是:

即使同时其他线程从列表中添加或删除元素,该修改也会制作数据的新副本,该副本将用于从该列表中进行任何进一步的数据查找。

这意味着如果您创建一个迭代器,然后以某种方式继续改变列表,迭代器将看不到这些更改。 为什么? 因为突变是通过创建一个具有突变的新数组来完成的,但是迭代器正在遍历没有突变的旧数组。 这就是为什么我们说迭代器需要一个“快照”。

这是来自 OpenJDK 的一些代码来说明。

iterator()中,它只是使用getArray()创建了一个COWIterator ,它通过返回 volatile array字段来获取快照:

final Object[] getArray() {
    return array;
}

...

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

并且 mutator 方法,例如add ,设置array字段:

final void setArray(Object[] a) {
    array = a;
}

...

public boolean add(E e) {
    Object[] elements = getArray();
    int len = elements.length;
    Object[] newElements = Arrays.copyOf(elements, len + 1);
    newElements[len] = e;
    setArray(newElements);
    return true;
}

我删除了(解锁)锁定代码,以便更容易看到正在发生的事情。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM