在这篇文章中,我们将查看java.util.concurrent包中的CopyOnWriteArrayList。
CopyOnWriteArrayList的设计使用一种有趣的技术使其成为线程安全的,无需同步。当我们使用任何修改方法时,例如add()或remove(),CopyOnWriteArrayList的全部内容将复制到新的内部副本中。
基于这个原因,我们可以线程安全地迭代列表,即使当前有并发修改发生。 当我们在CopyOnWriteArrayList上调研iterator()方法时,我们返回一个由CopyOnWriteArrayList内容的不可变快照备份的Iterator。
其内容是从创建Iterator时开始在ArrayList中的数据的完整副本。即使在此期间其他线程添加或删除列表中的元素,该修改也会生成数据的新副本,该副本将用于从该列表进行的任何进一步数据查找。
这种数据结构的特性使它特别适用于我们迭代它而不是修改它的情况,如果添加元素是我们场景中的常见操作,那么CopyOnWriteArrayList将不是一个好的选择 - 因为额外的副本肯定会导致低于标准的性能。
首先创建一个存储整数的CopyOnWriteArrayList。
CopyOnWriteArrayList<Integer> numbers = new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8}); 复制代码
接着,创建一个迭代器
Iterator<Integer> iterator = numbers.iterator(); 复制代码
最后,我们向list追加一个元素
numbers.add(10); 复制代码
由于我们创建迭代器后,我们拿到了数据的副本。因此,当我们迭代时,我们将看不到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); 复制代码
创建了CopyOnWriteArrayList是为了允许即使在底层列表被修改时也可以安全地迭代元素。
因为是复制机制,所以不允许对返回的迭代器执行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(); } } 复制代码