CopyOnWriteArrayList指南
1.概述
在這篇快速文章中,我們將查看java.util.concurrent包中的*CopyOnWriteArrayList* 。
在多線程程序中,這是一個非常有用的構造-當我們希望以線程安全的方式遍歷列表而無需顯式同步synchronization時。
2. CopyOnWriteArrayList API
CopyOnWriteArrayList的設計使用一種有趣的技術來使其具有線程安全性,而無需進行同步。當我們使用任何修改方法(例如add()或remove())時, CopyOnWriteArrayList的全部內容都將復製到新的內部副本中。
由於這個簡單的事實,即使發生並發修改,我們也可以以安全的方式遍歷列表。
當我們在CopyOnWriteArrayList上調用iterator()方法時,我們將返回一個由CopyOnWriteArrayList內容的不可變快照備份的Iterator 。
它的內容是從創建迭代器以來的ArrayList內的數據的精確副本。即使與此同時,其他某個線程在列表中添加或刪除了一個元素,該修改也將為該數據創建一個新的數據副本,該副本將用於該列表的任何進一步的數據查找中。
這種數據結構的特性使得它在我們迭代而不是修改它的情況下尤其有用。如果在我們的場景中添加元素是常見的操作,那麼CopyOnWriteArrayList將不是一個好選擇-因為額外的副本肯定會導致性能低於標準水平。
3.插入時遍歷CopyOnWriteArrayList
假設我們正在創建一個存儲整數的CopyOnWriteArrayList實例:
CopyOnWriteArrayList<Integer> numbers
= new CopyOnWriteArrayList<>(new Integer[]{1, 3, 5, 8});
接下來,我們要遍歷該數組,因此我們要創建一個Iterator實例:
Iterator<Integer> iterator = numbers.iterator();
創建Iterator之後,我們將一個新元素添加到數字列表中:
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的目的是,即使對基礎列表進行了修改,也可以對元素進行安全的迭代。
由於存在復制機制,因此不允許對返回的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();
}
}
5.結論
在本快速教程中,我們了解了java.util.concurrent包中的CopyOnWriteArrayList實現。
我們看到了該列表的有趣語義,以及如何以線程安全的方式對其進行迭代,而其他線程可以繼續從中插入或刪除元素。
所有這些示例和代碼段的實現都可以在GitHub項目中找到–這是一個Maven項目,因此應該很容易直接導入和運行。