【问题标题】:Why CopyOnWriteArrayList needs copies for both write and read operations?为什么 CopyOnWriteArrayList 需要用于写入和读取操作的副本?
【发布时间】:2021-04-05 12:47:26
【问题描述】:

来自article,它说:

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

由于这个简单的事实,我们可以安全地遍历列表, 即使正在发生并发修改。

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

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

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

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

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

【问题讨论】:

    标签: java iterator clone copyonwritearraylist java-failsafe


    【解决方案1】:

    CopyOnWriteArrayList 在调用iterator 时不会创建数组的副本,正如docs 所说:

    “快照”风格的迭代器方法使用一个引用来指向创建迭代器时数组的状态。

    注意“参考”这个词。

    这句话的措辞相当糟糕:

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

    这并不意味着当您调用iterator()创建了一个数组的副本。应该说:

    它的内容从迭代器创建时开始在 ArrayList 中的数据相同。

    那段更重要的一点是:

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

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

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

    iterator() 中,它只是用getArray() 创建一个COWIterator,它通过返回易失性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;
    }
    

    我删除了(解锁)锁定代码,以便更轻松地查看正在发生的事情。

    【讨论】:

    • 谢谢!但是为什么通常说fail-safe iterators traverse over the clone of the collection -example 是 CopyOnWriteArrayList。 克隆 = 复制,对吗?你刚才说根本没有副本。对不起,我还是很困惑:)
    • @Stefan 谁一般这么说的?在您链接的文章中,我什至找不到“故障安全”这个词。
    • 这里例如(向下滚动一点,直到找到故障安全内部工作):anmolsehgal.medium.com/…。 CopyOnWriteArrayList 从快照中读取并在原始数组上进行可变操作不是更容易吗?它在技术上应该仍然可以正常工作,因为快照没有“看到”任何突变。
    • @Stefan 他们要么(错误地)认为getArray 实际上正在创建一个副本,要么他们的意思是创建了对数组的引用的副本,这是正确的。我不是那篇文章的作者,所以我不能告诉你他们真正想说什么。
    • @Stefan 那不再是“写时复制”了,对吗?无论如何,这样做的缺点是你最终会得到更多的锁定。您还需要锁定 getter,否则它们可能会在“原始数组”发生突变时看到一些中间(无效)状态。
    猜你喜欢
    • 2021-10-02
    • 1970-01-01
    • 2010-11-01
    • 2023-02-06
    • 2015-04-30
    • 2022-12-18
    • 1970-01-01
    • 1970-01-01
    • 2019-01-28
    相关资源
    最近更新 更多