CopyOnWriteArraySet 是 JDK 1.5 引入的線程安全 Set 實現,底層委託 CopyOnWriteArrayList 存儲。本文深度解析其數據結構、寫時復制機制、迭代器弱一致性特性、適用場景與性能權衡,並通過併發測試案例驗證線程安全性。
add/remove 復制底層數組ConcurrentModificationExceptionUnsupportedOperationException
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private final CopyOnWriteArrayList<E> al; // 唯一存儲字段
public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<>(); }
public CopyOnWriteArraySet(Collection<? extends E> c) { ... }
// 關鍵:去重添加
public boolean add(E e) { return al.addIfAbsent(e); }
public boolean addAll(Collection<? extends E> c) { return al.addAllAbsent(c); }
// 委託實現
public boolean remove(Object o) { return al.remove(o); }
public int size() { return al.size(); }
public Iterator<E> iterator() { return al.iterator(); }
// ... 其他方法均委託 al
}
// 變更操作:加鎖 + 復制數組 + 修改 + volatile 發布
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements); // volatile 寫入,保證可見性
return true;
} finally { lock.unlock(); }
}
// 讀操作:無鎖直接讀取 volatile 數組引用
public E get(int index) { return getArray()[index]; }
private volatile Object[] array; // 關鍵:volatile 保證發布可見性
ReentrantLock 序列化所有寫操作public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : add(e);
}
private static int indexOf(E e, Object[] elements, int index, int fence) {
if (e == null) {
for (int i = index; i < fence; i++) if (elements[i] == null) return i;
} else {
for (int i = index; i < fence; i++) if (e.equals(elements[i])) return i;
}
return -1;
}
性能特點:去重檢查 O(n) 線性掃描,適合數據量小、讀多寫少場景。
static final class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot; // 構造時捕獲數組快照
private int cursor;
COWIterator(Object[] elements, int initialCursor) {
this.snapshot = elements;
this.cursor = initialCursor;
}
public boolean hasNext() { return cursor < snapshot.length; }
public E next() { return (E) snapshot[cursor++]; }
public void remove() { throw new UnsupportedOperationException(); }
}
volatile array 當前引用remove()/set()/add()Set<String> set = new CopyOnWriteArraySet<>();
// 兩線程並發添加並遍歷
new Thread(() -> { for (int i=0; i<10; i++) { set.add("TA-"+i%6); printAll(); } }).start();
new Thread(() -> { for (int i=0; i<10; i++) { set.add("TB-"+i%6); printAll(); } }).start();
結果:程序正常運行,無 ConcurrentModificationException,元素自動去重。
對比:將 set 改為 HashSet 並發操作必現 ConcurrentModificationException。
| 適用場景 | 不適用場景 |
|---|---|
| 集合較小(<1000 元素) | 大規模數據集(復制開銷大) |
| 讀操作遠多於寫操作 | 高頻寫入場景 |
| 需要遍歷時不阻塞寫入 | 需要實時強一致性視圖 |
| 作為緩存、監聽器列表、配置集合 | 作為高吞吐佇列、大數據去重 |
CopyOnWriteArraySet 通過寫時復制 + volatile 發布 + 快照迭代器三重機制,實現了「讀完全無鎖、寫線程安全、遍歷不拋異常」的獨特併發語義。理解其適用邊界(小數據、讀多寫少),是正確選型併發集合的關鍵。