CopyOnWriteArrayList
是线程安全的,在添加的时候,CopyOnWriteArrayList
会先将原来的数组进行拷贝,然后再在往拷贝的新数组里面添加元素,最后将拷贝的新数组重新指向原来的数组。并且在add
的时候会使用Synchronized
进行同步,但是在读取数据的时候,不会修改集合数据,所以不添加Synchronized
修饰从而可以进行并发的读取,这种读写分离解决了ConcurrentModificationException
(并发修改异常),因此,CopyOnWriteArrayList
适合使用在读操作远远大于写操作的场景里,如在缓存中使用。
不过它有很多的缺点:
写入数据的时候,每次都需要拷贝一份新的数据,如果数据量比较大,会很消耗内存,可能导致频繁的GC;
对于实时读取较高的场景,CopyOnWriteArrayList
中新增元素是需要拷贝数组的,所以在获取数据的时候,可能拷贝数据还未完成,这将会造成获取的数据还是之前那份数据,因此它无法满足实时性;
CopyOnWriteArrayList
合适读多写少的场景,不过我们还是需要谨慎使用。
本文基于android-23
源码分析。可能和JDK里面的实现不一致,如加锁,本文使用的是Synchronized
,而JDK1.6源码中使用的是ReentrantLock
可重入锁的加锁方式方式,不过大体思想一致。
源码分析
先来看看CopyOnWriteArrayList
的继承和实现关系:
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, Serializable {}
CopyOnWriteArrayList
和ArrayList
,LinkedList
一样同样实现了List
,此外它还分别实现了RandomAccess
, Cloneable
, Serializable
:
RandomAccess
:它是一个标记接口,接口内没有定义任何内容,它支持快速随机访问,具体什么意思,后面会讲到。
Cloneable
:通过实现clone()
方法,能够实现克隆对象;
Serializable
:ArrayList
支持序列化,和反序列化,实现Serializble
接口之后能够进行序列化传输;
先来看看CopyOnWriteArrayList
构造函数以及类成员变量:
/**
* CopyOnWriteArrayList内部的数组
*/
private transient volatile Object[] elements;
/**
* 创建一个空的实例
*/
public CopyOnWriteArrayList() {
elements = EmptyArray.OBJECT;
}
@SuppressWarnings("unchecked")
public CopyOnWriteArrayList(Collection<? extends E> collection) {
this((E[]) collection.toArray());
}
public CopyOnWriteArrayList(E[] array) {
this.elements = Arrays.copyOf(array, array.length, Object[].class);
}
通过上面的构造函数以及定义的elements
成员变量可以看出,用的是transient
以及volatile
修饰成员elements
数组,transient
字段表示被transient
修饰的属性在对象被序列化的时候不会被保存。volatile
是一个轻量级的内存同步手段,写操作在后一个volatile
的读操作之前,这样会保持变量在内存中的同步。
CopyOnWriteArrayList添加
CopyOnWriteArrayList
的添加操作:
public synchronized boolean add(E e) {
Object[] newElements = new Object[elements.length + 1];
System.arraycopy(elements, 0, newElements, 0, elements.length);
newElements[elements.length] = e;
elements = newElements;
return true;
}
public synchronized void add(int index, E e) {
Object[] newElements = new Object[elements.length + 1];
System.arraycopy(elements, 0, newElements, 0, index);
newElements[index] = e;
System.arraycopy(elements, index, newElements, index + 1, elements.length - index);
elements = newElements;
}
public synchronized boolean addAll(Collection<? extends E> collection) {
return addAll(elements.length, collection);
}
public synchronized boolean addAll(int index, Collection<? extends E> collection) {
Object[] toAdd = collection.toArray();
Object[] newElements = new Object[elements.length + toAdd.length];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(toAdd, 0, newElements, index, toAdd.length);
System.arraycopy(elements, index,
newElements, index + toAdd.length, elements.length - index);
elements = newElements;
return toAdd.length > 0;
}
public synchronized int addAllAbsent(Collection<? extends E> collection) {
Object[] toAdd = collection.toArray();
Object[] newElements = new Object[elements.length + toAdd.length];
System.arraycopy(elements, 0, newElements, 0, elements.length);
int addedCount = 0;
for (Object o : toAdd) {
if (indexOf(o, newElements, 0, elements.length + addedCount) == -1) {
newElements[elements.length + addedCount++] = o;
}
}
if (addedCount < toAdd.length) {
newElements = Arrays.copyOfRange(
newElements, 0, elements.length + addedCount); // trim to size
}
elements = newElements;
return addedCount;
}
/**
*
*/
public synchronized boolean addIfAbsent(E object) {
if (contains(object)) {
return false;
}
add(object);
return true;
}
上面的代码都是添加的实现,通过上面的代码,我们会发现add
方法均添加了synchronized
字段进行同步操作,由此可以看出CopyOnWriteArrayList
确实是支持多线程并发操作的。正如文章开头说的一样,CopyOnWriteArrayList
会先将原来的数组进行一份拷贝,然后再在拷贝的新数组里面添加元素,最后将拷贝的新数组重新指向原来的数组。
CopyOnWriteArrayList移除
public synchronized E remove(int index) {
@SuppressWarnings("unchecked")
E removed = (E) elements[index];
removeRange(index, index + 1);
return removed;
}
public synchronized boolean remove(Object o) {
int index = indexOf(o);
if (index == -1) {
return false;
}
remove(index);
return true;
}
public synchronized boolean removeAll(Collection<?> collection) {
return removeOrRetain(collection, false, 0, elements.length) != 0;
}
public synchronized boolean retainAll(Collection<?> collection) {
return removeOrRetain(collection, true, 0, elements.length) != 0;
}
/**
* Removes or retains the elements in {@code collection}. Returns the number
* of elements removed.
*/
private int removeOrRetain(Collection<?> collection, boolean retain, int from, int to) {
for (int i = from; i < to; i++) {
if (collection.contains(elements[i]) == retain) {
continue;
}
/*
* We've encountered an element that must be removed! Create a new
* array and copy in the surviving elements one by one.
*/
Object[] newElements = new Object[elements.length - 1];
System.arraycopy(elements, 0, newElements, 0, i);
int newSize = i;
for (int j = i + 1; j < to; j++) {
if (collection.contains(elements[j]) == retain) {
newElements[newSize++] = elements[j];
}
}
/*
* Copy the elements after 'to'. This is only useful for sub lists,
* where 'to' will be less than elements.length.
*/
System.arraycopy(elements, to, newElements, newSize, elements.length - to);
newSize += (elements.length - to);
if (newSize < newElements.length) {
newElements = Arrays.copyOfRange(newElements, 0, newSize); // trim to size
}
int removed = elements.length - newElements.length;
elements = newElements;
return removed;
}
// we made it all the way through the loop without making any changes
return 0;
}