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 协议 ,转载请注明出处!