CopyOnWriteArrayList使用教程

作者 | 2020年5月21日

1. 前言

在本篇教程中,我们将学习java.util.concurrent包中的CopyOnWriteArrayList。该数据结构在多线程应用中会经常用到。

2. CopyOnWriteArrayList API

CopyOnWriteArrayList使用了一种有趣的技术,无需使用同步语句就可以使其具有线程安全性。

当我们使用任何可以对List内容进行修改的方法时,例如:add()或remove(),CopyOnWriteArrayList的全部内容都将复制到新的内部副本中.

由此,我们可以安全的遍历列表,即使出现了并发修改。

当我们在CopyOnWriteArrayList上调用iterator()方法时,将会为我们生成一个CopyOnWriteArrayList内容的不可变快照。

当遍历操作比修改操作发生的更频繁的时候,CopyOnWriteArrayList的这个特点将会变得非常有用。

对于添加操作使用非常频繁的场景,CopyOnWriteArrayList通常不会是一个很好的选择,因为额为的副本肯定会导致一些性能问题。

3. 在迭代的同时插入元素

我们先创建一个CopyOnWriteArrayList用于存储整数:

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

接下来,为了对其进行迭代,所以我们再创建一个迭代器实例:

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);

而接下来新创建的迭代器则可以看到我们新添加的数字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的目的是,即使对底层列表进行了修改,也可以对元素安全的进行迭代。

由于存在复制机制,因此不允许对返回的Iterator进行remove()操作,如果试图调用该方法,那么会导致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();
    }
}

发表评论

电子邮件地址不会被公开。 必填项已用*标注