CopyOnWriteArraySet介紹
它是線程安全的無序的集合,可以將它理解成線程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet雖然都繼承於共同的父類AbstractSet;但是,HashSet是通過“散列表(HashMap)”實現的,而CopyOnWriteArraySet則是通過“動態數組(CopyOnWriteArrayList)”實現的,並不是散列表。
和CopyOnWriteArrayList類似,CopyOnWriteArraySet具有以下特性:
1. 它最適合於具有以下特征的應用程序:Set 大小通常保持很小,只讀操作遠多於可變操作,需要在遍歷期間防止線程間的沖突。
2. 它是線程安全的。
3. 因為通常需要復制整個基礎數組,所以可變操作(add()、set() 和 remove() 等等)的開銷很大。
4. 迭代器支持hasNext(), next()等不可變操作,但不支持可變 remove()等 操作。
5. 使用迭代器進行遍歷的速度很快,並且不會與其他線程發生沖突。在構造迭代器時,迭代器依賴於不變的數組快照。
CopyOnWriteArraySet原理和數據結構
CopyOnWriteArraySet的數據結構,如下圖所示:
說明:
1. CopyOnWriteArraySet繼承於AbstractSet,這就意味著它是一個集合。
2. CopyOnWriteArraySet包含CopyOnWriteArrayList對象,它是通過CopyOnWriteArrayList實現的。而CopyOnWriteArrayList本質是個動態數組隊列,
所以CopyOnWriteArraySet相當於通過通過動態數組實現的“集合”! CopyOnWriteArrayList中允許有重復的元素;但是,CopyOnWriteArraySet是一個集合,所以它不能有重復集合。因此,CopyOnWriteArrayList額外提供了addIfAbsent()和addAllAbsent()這兩個添加元素的API,通過這些API來添加元素時,只有當元素不存在時才執行添加操作!
至於CopyOnWriteArraySet的“線程安全”機制,和CopyOnWriteArrayList一樣,是通過volatile和互斥鎖來實現的。這個在前一章節介紹CopyOnWriteArrayList時數據結構時,已經進行了說明,這裡就不再重復敘述了。
CopyOnWriteArraySet函數列表
// 創建一個空 set。 CopyOnWriteArraySet() // 創建一個包含指定 collection 所有元素的 set。 CopyOnWriteArraySet(Collection<? extends E> c) // 如果指定元素並不存在於此 set 中,則添加它。 boolean add(E e) // 如果此 set 中沒有指定 collection 中的所有元素,則將它們都添加到此 set 中。 boolean addAll(Collection<? extends E> c) // 移除此 set 中的所有元素。 void clear() // 如果此 set 包含指定元素,則返回 true。 boolean contains(Object o) // 如果此 set 包含指定 collection 的所有元素,則返回 true。 boolean containsAll(Collection<?> c) // 比較指定對象與此 set 的相等性。 boolean equals(Object o) // 如果此 set 不包含任何元素,則返回 true。 boolean isEmpty() // 返回按照元素添加順序在此 set 中包含的元素上進行迭代的迭代器。 Iterator<E> iterator() // 如果指定元素存在於此 set 中,則將其移除。 boolean remove(Object o) // 移除此 set 中包含在指定 collection 中的所有元素。 boolean removeAll(Collection<?> c) // 僅保留此 set 中那些包含在指定 collection 中的元素。 boolean retainAll(Collection<?> c) // 返回此 set 中的元素數目。 int size() // 返回一個包含此 set 所有元素的數組。 Object[] toArray() // 返回一個包含此 set 所有元素的數組;返回數組的運行時類型是指定數組的類型。 <T> T[] toArray(T[] a)
CopyOnWriteArraySet是通過CopyOnWriteArrayList實現的,它的API基本上都是通過調用CopyOnWriteArrayList的API來實現的。相信對CopyOnWriteArrayList了解的話,對CopyOnWriteArraySet的了解是水到渠成的事;所以,這裡就不再對CopyOnWriteArraySet的代碼進行詳細的解析了。
CopyOnWriteArraySet示例
下面,我們通過一個例子去對比HashSet和CopyOnWriteArraySet。
import java.util.*; import java.util.concurrent.*; /* * CopyOnWriteArraySet是“線程安全”的集合,而HashSet是非線程安全的。 * * 下面是“多個線程同時操作並且遍歷集合set”的示例 * (01) 當set是CopyOnWriteArraySet對象時,程序能正常運行。 * (02) 當set是HashSet對象時,程序會產生ConcurrentModificationException異常。 * * */ public class CopyOnWriteArraySetTest1 { // TODO: set是HashSet對象時,程序會出錯。 //private static Set<String> set = new HashSet<String>(); private static Set<String> set = new CopyOnWriteArraySet<String>(); public static void main(String[] args) { // 同時啟動兩個線程對set進行操作! new MyThread("ta").start(); new MyThread("tb").start(); } private static void printAll() { String value = null; Iterator iter = set.iterator(); while(iter.hasNext()) { value = (String)iter.next(); System.out.print(value+", "); } System.out.println(); } private static class MyThread extends Thread { MyThread(String name) { super(name); } @Override public void run() { int i = 0; while (i++ < 10) { // “線程名” + "-" + "序號" String val = Thread.currentThread().getName() + "-" + (i%6); set.add(val); // 通過“Iterator”遍歷set。 printAll(); } } } }
(某一次)運行結果:
ta-1, tb-1, ta-1, tb-1, ta-1, tb-1, ta-1, ta-2, tb-1, ta-1, ta-2, tb-1, tb-2, ta-2, ta-1, tb-2, tb-1, ta-3, ta-2, ta-1, tb-2, tb-1, ta-3, ta-2, tb-3, tb-2, ta-1, ta-3, tb-1, tb-3, ta-2, ta-4, tb-2, ta-1, ta-3, tb-1, tb-3, ta-2, ta-4, tb-2, tb-4, ta-3, ta-1, tb-3, tb-1, ta-4, ta-2, tb-4, tb-2, ta-5, ta-3, ta-1, tb-3, tb-1, ta-4, ta-2, tb-4, tb-2, ta-5, ta-3, tb-5, tb-3, ta-1, ta-4, tb-1, tb-4, ta-2, ta-5, tb-2, tb-5, ta-3, ta-0, tb-3, ta-1, ta-4, tb-1, tb-4, ta-2, ta-5, tb-2, tb-5, ta-3, ta-0, tb-3, tb-0, ta-4, ta-1, tb-4, tb-1, ta-5, ta-2, tb-5, tb-2, ta-0, ta-3, tb-0, tb-3, ta-1, ta-4, tb-1, tb-4, ta-2, ta-5, tb-5, ta-0, tb-0, ta-1, tb-2, tb-1, ta-3, ta-2, tb-3, tb-2, ta-4, ta-3, tb-4, tb-3, ta-5, ta-4, tb-5, tb-4, ta-0, ta-5, tb-0, tb-5, ta-1, ta-0, tb-1, tb-0, ta-2, ta-1, tb-2, tb-1, ta-3, ta-2, tb-3, tb-2, ta-4, ta-3, tb-4, tb-3, ta-5, tb-5, ta-0, tb-0, ta-4, ta-1, tb-4, tb-1, ta-5, ta-2, tb-5, tb-2, ta-0, ta-3, tb-0, tb-3, ta-1, ta-4, tb-1, tb-4, ta-2, ta-5, tb-2, tb-5, ta-3, ta-0, tb-3, tb-0, ta-4, tb-4, ta-5, tb-5, ta-0, tb-0,
結果說明:
由於set是集合對象,因此它不會包含重復的元素。
如果將源碼中的set改成HashSet對象時,程序會產生ConcurrentModificationException異常。