从字面上看,CopyOnWriteArrayList就是写时复制。当有新元素添加时,首先是将原来的容器复制一份,然后在新容器中进行添加操作,添加完之后,再将指向旧容器的引用给指向新容器。好处是:并发读时不加锁,做到读写分离。
结合源码依次来解析CopyOnWriteArrayList的内容:
1.构造方法:
CopyOnWriteArrayList有三个构造方法,分别是无参构造(创建一个空的),单参构造(传入一个集合或数组)
final void setArray(Object[] a) {
array = a;
}
/**
* 创建一个空的数组
*
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* 创建一个包含指定元素的列表,可将传入Collection的实现类
* 集合,会按照集合返回的顺序
* 指针
*
* @param c 初始保存元素的集合
* @throws 空指针异常
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] es;
if (c.getClass() == CopyOnWriteArrayList.class)
es = ((CopyOnWriteArrayList<?>)c).getArray();
else {
es = c.toArray();
if (c.getClass() != java.util.ArrayList.class)
es = Arrays.copyOf(es, es.length, Object[].class);
}
setArray(es);
}
/**
* 创建一个包含给定数组副本的列表。
*
* @param 在数组中(该数组的副本用作内部数组)
*
* @throws 空指针异常
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
2.添加元素
//使用Object对象作为锁
final transient Object lock = new Object();
//一个指向当前容量引用的成员变量(指针)
private transient volatile Object[] array;
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
public boolean add(E e) {
synchronized (lock) {
//获取当前列表元素
Object[] es = getArray();
//获取当前列表长度
int len = es.length;
//将旧数组拷贝到新数组中(新数组长度为旧数组长度+1)
es = Arrays.copyOf(es, len + 1);
//在旧长度的位置下添加新元素
es[len] = e;
//将新数组重新赋值给成员变量,实际上将指向旧容量的引用改为指向新容量。
setArray(es);
//返回修改成功标志
return true;
}
}
3.批量添加
//批量添加(传入一个集合)
public boolean addAll(Collection<? extends E> c) {
//判断其是否是CopyOnWriteArrayList类型,如果是的就调用内部特有方法获取内置数组
//否则调用集合接口的方法
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
//数组的长度为0则无需添加,直接返回
if (cs.length == 0)
return false;
//添加前进行加锁
synchronized (lock) {
//获取旧容量
Object[] es = getArray();
int len = es.length;
//创建新容量
Object[] newElements;
//如果旧容量等于0则无需复制
if (len == 0 && (c.getClass() == CopyOnWriteArrayList.class ||
c.getClass() == ArrayList.class)) {
newElements = cs;
} else { //将旧数组的元素复制到新数组中,同时新数组的长度是len + cs.length
newElements = Arrays.copyOf(es, len + cs.length);
//将要复制的数组从0下标开始全部复制到新数组当中
System.arraycopy(cs, 0, newElements, len, cs.length);
}
//修改成员变量的引用
setArray(newElements);
//返回成功标志
return true;
}
}
4.获取元素
//返回指定下标下的数组元素
static <E> E elementAt(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws 数组越界异常
*/
public E get(int index) {
//传入当前容量数组和想要获取的索引(下标)
return elementAt(getArray(), index);
}
5.删除元素
//传入指定下标并删除该下标的上的元素
public E remove(int index) {
//删除前加锁
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
//记录要被删除的元素值
E oldValue = elementAt(es, index);
int numMoved = len - index - 1;
Object[] newElements;
if (numMoved == 0)
//表示删除的元素是最后一个,原数组的位置不动
newElements = Arrays.copyOf(es, len - 1);
else {
//非末尾位置,则删除了该元素之后,其后面的元素需要整体向前移动
newElements = new Object[len - 1];
//复制索引前的元素到新数组
System.arraycopy(es, 0, newElements, 0, index);
//将旧元素index往后的元素,接着复制到新数组
System.arraycopy(es, index + 1, newElements, index,
numMoved);
}
//修改当前成员变量的引用
setArray(newElements);
//将记录的删除元素进行返回
return oldValue;
}
}