CopyOnWriteArrayList是一种用于集合的并发访问的优化策略。基本思想是:当我们往一个集合容器中写入元素时(添加、修改、删除),并不会直接在集合容器中写入,而是先将当前集合容器进行Copy,复制出一个新的容器,然后新的容器里写入元素,写入操作完成之后,再将原容器的引用指向新的容器。
好处:实现对CopyOnWrite集合容器写入操作时的线程安全(使用ReentrantLock实现),但同时并不影响进行并发的读取操作。
允许多个线程并发读取,但只能有一个线程写入。
以下是对CopyOnWriteArrayList源码的理解:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 可重入锁,保证线程安全
final transient ReentrantLock lock = new ReentrantLock();
// transient让定义的变量不被序列化
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;
}
// 无参构造方法,创建一个空数组
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
//根据指定下标,到原数组中读取元素
/*
* 读取的过程不加锁,允许多个线程并发读取,
* 但是如果读取的时候,有其它线程向集合中添加新元素,此时仍然读取到的是旧数据。
* 因为添加操作没有对原数组加锁。
*/
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];//传入数组和指定下标,返回数组中指定下标的元素
}
public E get(int index) {
// 根据指定下标,调用上面的get()从原数组中读取元素
return get(getArray(), index);
}
public E set(int index, E element) {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原数组
Object[] elements = getArray();
// 得到原数组中指定下标的元素
E oldValue = get(elements, index);
// 如果传入的值不等于这个数组中原来的值
if (oldValue != element) {
// 获取原数组长度
int len = elements.length;
// 创建一个新数组将原数组中的值复制到新数组中
Object[] newElements = Arrays.copyOf(elements, len);
// 将新数组中指定下标的元素替换成传入的新值
newElements[index] = element;
// 把原数组引用指向新数组
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
// 如果要传入的新值和原数组中指定位置的值相同,则直接把原数组引用指向原数组
setArray(elements);
}
return oldValue;
} finally {
// 关锁
lock.unlock();
}
}
// 添加新元素
/* 主要思想:将当前数组Copy复制新数组,并将新元素添加至新数组,最后替换原数组。 */
public boolean add(E e) {
// 使用ReentrantLock加锁,保证线程安全,避免多个线程复制数组
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();
}
}
public void add(int index, E element) {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原数组
Object[] elements = getArray();
int len = elements.length;
// 想要添加值的位置如果大于原数组的长度或者小于0,则抛出异常
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;//创建新的数组
int numMoved = len - index;
if (numMoved == 0)//如果想要添加的指定位置在最后,就直接将原数组复制到新数组上,并将长度加1
newElements = Arrays.copyOf(elements, len + 1);
else {//如果想加在最后
newElements = new Object[len + 1];//新数组的长度是老数组的长度加1
System.arraycopy(elements, 0, newElements, 0, index);//将0到index之间(不包含index)的元素,从新数组下标为0开始复制到新数组中
System.arraycopy(elements, index, newElements, index + 1,
numMoved);//将老数组从下标为index开始一直到len的元素,从新数组下标为index+1开始复制到新数组中
}
newElements[index] = element;//设置新数组index位置的元素为想要加入的那个元素
setArray(newElements);//把原数组引用指向新数组
} finally {
// 关锁
lock.unlock();
}
}
// 删除指定下标元素
/* 根据指定下标,从原数组中,Copy复制其它元素至新数组,最后替换原数组 */
public E remove(int index) {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
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];//因为要删除一个元素,所以创建出的新数组是老数组的长度减1
System.arraycopy(elements, 0, newElements, 0, index);//复制老数组中0到index之间的元素(不包括index)至新数组中
System.arraycopy(elements, index + 1, newElements, index,//复制老数组中index+1后面的所有元素
numMoved);
setArray(newElements);//把原数组引用指向新数组
}
// 返回要删除的那个元素
return oldValue;
} finally {
// 关闭锁
lock.unlock();
}
}
//删除指定范围
void removeRange(int fromIndex, int toIndex) {
// 设置锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原数组
Object[] elements = getArray();
int len = elements.length;
// 如果想要删除的范围的起始位置小于0 或者 终止位置大于原数组的长度 或者 终止位置小于起始位置就抛出异常
if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
throw new IndexOutOfBoundsException();
// 新数组的长度为原数组长度减去想要删除的范围的长度
int newlen = len - (toIndex - fromIndex);
// 需要移动的元素个数(即删除指定范围后,指定范围之后没删除的元素需要向前移动的元素个数)
int numMoved = len - toIndex;
if (numMoved == 0)//如果需要移动的元素个数为0,则删除的范围在原数组的最后面
setArray(Arrays.copyOf(elements, newlen));//将原数组中0到newlen的长度复制给新数组
else {
// 如果不是,则先创建出长度为newlen的新数组
Object[] newElements = new Object[newlen];
//将原数组0到删除范围的起始位置复制给新数组
System.arraycopy(elements, 0, newElements, 0, fromIndex);
// 将原数组从删除范围的终止位置到len复制给新数组的fromIndex位置到newlen
System.arraycopy(elements, toIndex, newElements,
fromIndex, numMoved);
setArray(newElements);//把原数组引用指向新数组
}
} finally {
lock.unlock();//关锁
}
}
//传入一个集合,删除原数组中在集合中有的元素(即集合和原数组的交集)
public boolean removeAll(Collection<?> c) {
if (c == null) throw new NullPointerException();//如果这个集合为空则抛出异常
// 设置锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原数组、原数组长度
Object[] elements = getArray();
int len = elements.length;
// 如果原数组不为空,就遍历原数组判断集合中是否包含原数组中的元素,如果不包含就将这个元素存入到新数组中
if (len != 0) {
// temp array holds those elements we know we want to keep
int newlen = 0;
Object[] temp = new Object[len];
for (int i = 0; i < len; ++i) {
Object element = elements[i];
if (!c.contains(element))
temp[newlen++] = element;
}
// 如果新数组和原数组的长度不一样(即有元素被删除)就把原数组引用指向新数组,并返回true
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
// 如果原数组为空则返回false
return false;
} finally {
lock.unlock();
}
}
// 清除数组
public void clear() {
// 设置锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
setArray(new Object[0]);//把原数组引用指向一个空的新数组
} finally {
lock.unlock();
}
}
// 将传入的集合中的元素加入到数组中
public boolean addAll(Collection<? extends E> c) {
// 判断传入的集合的类型和当前类的类型是否相同,相同的话得到数组值,不同的话将类型转变为数组
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
if (cs.length == 0)//如果获取的数组为空则返回false
return false;
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原数组
Object[] elements = getArray();
int len = elements.length;
// 判断旧数组长度 和 传入的集合的类型
if (len == 0 && cs.getClass() == Object[].class)
setArray(cs);
else {
// 拷贝原数组的内容到新数组中
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
System.arraycopy(cs, 0, newElements, len, cs.length);
setArray(newElements);
}
return true;
} finally {
lock.unlock();
}
}
public void sort(Comparator<? super E> c) {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 获取原数组,并将原数组复制给一个新数组
Object[] elements = getArray();
Object[] newElements = Arrays.copyOf(elements, elements.length);
//添加注解,作用:去除警告
@SuppressWarnings("unchecked") E[] es = (E[])newElements;
// 对数组进行排序
Arrays.sort(es, c);
// 把原数组引用指向新数组
setArray(newElements);
} finally {
lock.unlock();
}
}
// 判断两个数组是否相同
public boolean equals(Object o) {
// 如果传入的与要比较的元素的内存地址相同,则这两个值一定相同
if (o == this)
return true;
// 判断传入的对象是否是List类型(如果不是则类型都不同,则直接返回false)
if (!(o instanceof List))
return false;
// 向下转型
List<?> list = (List<?>)(o);
// 创建迭代器遍历数组
Iterator<?> it = list.iterator();
Object[] elements = getArray();
int len = elements.length;
for (int i = 0; i < len; ++i)
// 如果两个数组的长度不一样则两个数组一定不一样,返回false
if (!it.hasNext() || !eq(elements[i], it.next()))
return false;
if (it.hasNext())
return false;
return true;
}
// 计算hash值
public int hashCode() {
int hashCode = 1;
// 获取原数组
Object[] elements = getArray();
int len = elements.length;
// 遍历数组
for (int i = 0; i < len; ++i) {
Object obj = elements[i];
// 计算hash值
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
// 返回计算的hash值
return hashCode;
}
}