首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 併發集合深度解析:CopyOnWriteArraySet 線程安全無序集合原理與實戰

     編輯:Java 相關知識

Java 併發集合框架:CopyOnWriteArraySet 完整技術剖析

CopyOnWriteArraySet 是 JDK 1.5 引入的線程安全 Set 實現,底層委託 CopyOnWriteArrayList 存儲。本文深度解析其數據結構、寫時復制機制、迭代器弱一致性特性、適用場景與性能權衡,並通過併發測試案例驗證線程安全性。

核心特性速覽

  • 線程安全:所有變更操作加鎖,讀操作無鎖
  • 無序:元素按添加順序存儲,非哈希分佈
  • 寫時復制:add/remove 復制底層數組
  • 弱一致性迭代器:遍歷快照,不拋 ConcurrentModificationException
  • 不支援迭代器 remove():拋 UnsupportedOperationException

數據結構與實現原理

CopyOnWriteArraySet結構圖

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
}

寫時復制(COW)機制深度解析

CopyOnWriteArrayList 核心邏輯

// 變更操作:加鎖 + 復制數組 + 修改 + 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 保證發布可見性

線程安全三要素

  1. 互斥鎖:ReentrantLock 序列化所有寫操作
  2. volatile 數組引用:新數組發布對讀線程立即可見
  3. 不可變快照:迭代器遍歷構造時的數組引用,不受後續修改影響

去重機制:addIfAbsent / addAllAbsent

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 發布 + 快照迭代器三重機制,實現了「讀完全無鎖、寫線程安全、遍歷不拋異常」的獨特併發語義。理解其適用邊界(小數據、讀多寫少),是正確選型併發集合的關鍵。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved