CopyOnWriteArrayList的行为

爪哇语说CopyOnWriteArrayList

ArrayList 的线程安全变体,其中所有突变操作(add、set 等)都是通过创建基础数组的新副本来实现的。

我现在感到困惑,其他线程何时会看到此新副本中存在的更改?这是否意味着基础数组的副本数将等于集合的突变数?如果不是这样,这些单个副本的更改何时传输到底层阵列,以便其他线程可以看到它们?


答案 1

这里的想法是,每当您添加或删除时,基础数组基本上都会通过修改进行复制。CopyOnWriteArrayList

这是否意味着基础数组的副本数将等于集合的突变数

是的,对于每个更新的线程,持有旧副本的所有其他线程实质上将引用不同的数组。ArrayList

这些单个拷贝的更改何时传输到底层阵列,以便其他线程可以看到它们?

您当前正在查看的数组(假设您的迭代器)永远不会更改。当您从数组中读取时,您正在读取它,就像您开始读取时一样。如果另一个线程更改,则您当前正在观察的数组将不受影响。CopyOnWriteArrayList

要获得最新版本,请像这样进行新的阅读list.iterator();

话虽如此,经常更新此集合会扼杀性能。如果您尝试对 a 进行排序,您将看到列表抛出一个(排序调用集合 N 次上设置的)。仅当执行超过 90% 的读取时,才应使用此读取。CopyOnWriteArrayListUsupportedOperationException


答案 2

写入数组列表上复制的实现使用基础数组,并使用 setter 和 getter 方法访问它。

/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

/**
 * Gets the array.  Non-private so as to also be accessible
 * from CopyOnWriteArraySet class.
 */
final Object[] getArray() {
    return array;
}

/**
 * Sets the array.
 */
final void setArray(Object[] a) {
    array = a;
}

因此,对于添加,set操作,它创建当前数组的副本(使用getArray),添加元素并返回更新的数组(使用setArray)。

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);
        return true;
    } finally {
        lock.unlock();
    }
}

是的,在添加或设置方法之前访问过数组列表的线程将具有数据的陈旧副本(如果您可以处理一定程度的过时数据,这很好,因为CopyOnWriteArrayList的设计理念是遍历操作将超过添加或更新操作的数量),并且所有将创建迭代器或使用get操作的线程都将具有arrayList的最新数据。

public E get(int index) {
    return get(getArray(), index);
}

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

在这里 getarray 将给出 arrayList 的最新状态。


推荐