Ste*_*fan 3 java iterator clone copyonwritearraylist java-failsafe
来自这篇文章,它说:
\n\n\n当我们\n使用任何修改方法\xe2\x80\x93(例如add()或remove()\xe2\x80\x93)时,\nCopyOnWriteArrayList的整个内容都会被复制到新的\n内部副本中。
\n由于这个简单的事实,我们可以以安全的方式迭代列表,\n即使发生并发修改也是如此。
\n当我们在 CopyOnWriteArrayList 上调用 iterator() 方法时,我们会返回一个由 CopyOnWriteArrayList 内容的不可变快照备份的迭代器。
\n它的内容是创建迭代器时 ArrayList 内数据的精确副本。\n 即使与此同时,某个线程从列表中添加或删除元素,该修改也会创建数据的新副本,该副本将用于从该列表进行任何进一步的数据查找。
\n
接下来要问自己的简单问题是为什么两者都是?基本上,根据我的理解,写入操作是在新副本上进行的,而读取操作是在集合的克隆上完成的。
\n例如,如果在新副本上完成写入,则意味着我可以迭代“原始”集合 - 这意味着它不会受到影响。那么为什么要增加在另一个副本(快照)中存储元素的开销呢?或者相反的方向,如果我将元素存储在副本(快照)中,为什么当我实际上是在克隆而不是“原始”集合上迭代时需要在副本上进行写入(意味着快照永远不会改变)?
\n我希望这个问题是合法的,因为我确实检查了互联网上所有可能的来源,但没有一篇文章帮助我消除了这种困惑。我在这里缺少什么?
\nCopyOnWriteArrayList当您调用 时,不会创建数组的副本iterator,如文档所述:
“快照”样式迭代器方法使用对创建迭代器时数组状态的引用。
注意“参考”这个词。
这句话的措辞相当糟糕:
它的内容是自创建迭代器时起 ArrayList 内的数据的精确副本。
这并不意味着当您调用 时会创建数组的副本iterator()。应该说:
它的内容与创建 Iterator 时 ArrayList 内的数据相同。
该段更重要的一点是:
即使同时某个其他线程从列表中添加或删除元素,该修改也会创建数据的新副本,该副本将用于从该列表进行任何进一步的数据查找。
这意味着,如果您创建一个迭代器,然后继续以某种方式改变列表,则迭代器将不会看到这些更改。为什么?因为突变是通过创建具有突变的新数组来完成的,但迭代器正在迭代没有突变的旧数组。这就是为什么我们说迭代器采用“快照”。
下面是来自 OpenJDK 的一些代码来进行说明。
在 中iterator(),它只是创建一个COWIteratorwith getArray(),它通过返回 volatile 字段来获取快照array:
final Object[] getArray() {
return array;
}
...
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
Run Code Online (Sandbox Code Playgroud)
和 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;
}
Run Code Online (Sandbox Code Playgroud)
我删除了(解锁)锁定代码,以便更轻松地查看发生的情况。