CopyOnWriteArrayList的行为

时间:2010-11-19 17:43:46

标签: java data-structures collections concurrency

CopyOnWriteArrayList的Javadocs说

  

ArrayList的线程安全变体,其中包含所有可变操作   (添加,设置等)是通过制作新的副本来实现的   底层数组。

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

2 个答案:

答案 0 :(得分:17)

这里的想法是,无论何时添加或删除CopyOnWriteArrayList,底层数组基本上都会随修改一起复制。

  

这是否意味着会有多少   底层数组的副本相等   到了突变的数量   集合

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

  

这些变化何时发生   个人副本转移到   底层数组使其他线程   可以看到他们吗?

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

要获得最新版本,请执行list.iterator();

之类的新阅读

话虽如此,更新此系列将会扼杀性能。如果您尝试对CopyOnWriteArrayList进行排序,则会看到列表抛出UsupportedOperationException(对集合调用的排序调用次数为N次)。只有在读取90%以上的读数时才应该使用此读数。

答案 1 :(得分:0)

在写数组列表上复制的实现使用底层数组,并使用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;
}

因此,对于添加,设置操作,它会创建当前数组的副本(使用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();
    }
}

是,在add或set方法之前访问过arraylist的线程将具有陈旧的数据副本(如果可以处理一定程度的陈旧数据,这很好,因为CopyOnWriteArrayList的设计思路是遍历操作将胜过添加或更新操作),所有将创建迭代器或使用get操作的线程将具有arrayList的最新数据。

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

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

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