指南:CopyOnWriteArrayList

评论 0 浏览 0 2017-05-05

1.概述

在这篇快速的文章中,我们将看看来自java.util.concurrent包中的CopyOnWriteArrayList

在多线程程序中,这是一个非常有用的结构;当我们想以线程安全的方式迭代一个列表,而不需要明确的同步。

2. CopyOnWriteArrayList API

CopyOnWriteArrayList 的设计使用了一种有趣的技术,使其成为线程安全的,不需要同步。当我们使用任何一个修改方法时,例如add()remove()CopyOnWriteArrayList的全部内容被复制到新的内部副本中。

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

当我们在CopyOnWriteArrayList上调用iterator() 方法时,我们得到一个Iterator,由CopyOnWriteArrayList内容的不可改变的快照来支持。

它的内容是ArrayList中的数据的精确拷贝,从Iterator被创建时开始。即使在此期间,其他线程从列表中添加或删除了一个元素,该修改也是在制作一个新的数据副本,将用于从该列表中的任何进一步的数据查找。

这种数据结构的特点使得它在我们对它的迭代比修改更频繁的情况下特别有用。如果在我们的场景中,添加元素是一个常见的操作,那么CopyOnWriteArrayList 不会是一个好的选择–因为额外的拷贝肯定会导致性能不理想。

3.遍历CopyOnWriteArrayList,同时插入

假设我们正在创建一个CopyOnWriteArrayList的实例,该实例存储的是整数。

CopyOnWriteArrayList<Integer> numbers 
  = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8});

接下来,我们要对该数组进行迭代,所以我们要创建一个Iterator 实例。

Iterator<Integer> iterator = numbers.iterator();

迭代器创建之后,我们要在numbers 列表中添加一个新的元素。

numbers.add(10);

请记住,当我们为CopyOnWriteArrayList创建一个迭代器时,我们得到了iterator()被调用时列表中的数据的不可改变的快照。

正因为如此,在对其进行迭代时,我们不会在迭代中看到数字10

List<Integer> result = new LinkedList<>();
iterator.forEachRemaining(result::add);
 
assertThat(result).containsOnly(1, 3, 5, 8);

随后使用新创建的Iterator进行的迭代也将返回被添加的数字10。

Iterator<Integer> iterator2 = numbers.iterator();
List<Integer> result2 = new LinkedList<>();
iterator2.forEachRemaining(result2::add);

assertThat(result2).containsOnly(1, 3, 5, 8, 10);

4.不允许在迭代过程中进行删除

创建CopyOnWriteArrayList的目的是为了允许在底层列表被修改时也能安全地迭代元素。

由于复制机制,对返回的remove() 操作Iterator 是不允许的 – 导致UnsupportedOperationException:

@Test(expected = UnsupportedOperationException.class)
public void whenIterateOverItAndTryToRemoveElement_thenShouldThrowException() {
    
    CopyOnWriteArrayList<Integer> numbers
      = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8});

    Iterator<Integer> iterator = numbers.iterator();
    while (iterator.hasNext()) {
        iterator.remove();
    }
}

5.总结

在这个快速教程中,我们看了一下来自java.util.concurrent包的CopyOnWriteArrayList 实现。

我们看到了这个列表的有趣语义,以及它如何以线程安全的方式进行迭代,而其他线程可以继续插入或删除其中的元素。

所有这些例子和代码片段的实现都可以在GitHub项目中找到--这是一个Maven项目,所以应该很容易导入和运行,就像现在一样。

最后更新2022-12-11
0 个评论
标签