CopyOnWriteArrayList这个类,我还是看系统源码的时候看到的,但是一直不知道他是干什么用的,今天打算看一下它的实现。

我第一次看到CopyOnWriteArrayList的时候是在android.view.View.ListenerInfo#mOnAttachStateChangeListeners,当时的想法就是这个是一个listeners,而listeners肯定要经常的遍历,还有增删listener。后面我在写一些观察者模式代码时,也尝试使用了CopyOnWriteArrayList。

什么是CopyOnWrite呢,直接性的理解就是:在写入数据的时候先copy,copy什么呢,copy原来的数据。

我们来看一下write的情况 add()和remove()

public boolean add(E e) {
    synchronized (lock) {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    }
}

代码不是很复杂,首先加了个锁,处理并发写入,只允许一个线程进行写入操作。对现有的数据进行copy,然后对copy出来的数据进行赋值,最后用新数据替换掉旧数据。

public E remove(int index) {
    synchronized (lock) {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    }
}

也是对操作加锁,这里和add方法用了同一个锁,也就是说,add和remove只能有一个在执行。剩下的也是先copy得到新数组,然后操作新数组,然后用新数组覆盖旧数组。

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

private E get(Object[] a, int index) {
    return (E) a[index];
}

get方法简单粗暴。没有锁。

总得来说,CopyOnWriteArrayList是一个线程安全的list,写入时有锁控制,缺陷就是数据会更新不及时,而且每次都copy也比较耗费资源(内存)。但是查询比较快,所以这个list适合读取频繁,增删少的情况,所以作为listener的容器还是很合适的。

既然说到了多线程安全,那就还有一个头疼的问题,iterator!

我们知道有好多线程安全的类,比如vector。但是在做iterator的仍然会抛出ConcurrentModificationException。但是CopyOnWriteArrayList不会。

我们来看一下它的实现。

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

首先,在iterator时,他创建了一个COWIterator,并且使用了getArray,那就说明了一个问题,在多线程调用add、remove时,并不会修改这个COWIterator的array,因为他们不是同一个对象。


static final class COWIterator<E> implements ListIterator<E> {

    COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code remove}
     *         is not supported by this iterator.
     */
    public void remove() {
        throw new UnsupportedOperationException();
    }


    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
}

通过以上代码,我们看到array直接被命名成了snapshot,这个很形象。next方法就直接获取了array中的数据,不会出现ConcurrentModificationException。但是他竟然没有实现remove(),这个很蛋疼,但是也没办法,因为她是个snapshot,这个remove很可能会失效,很可能被add、remove操作覆盖掉。所以没办法实现。

所以 我们还是少用iterator吧,这个有太多的问题了,我现在唯一能想到的安全调用iterator的方式就是:

使用vector,然后对整个iterator加上锁

synchronized (vector) {
	// do iterator
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

HashMap实现原理 上一篇
面向对象思想中的抽象类和接口 下一篇