Copy-On-Write简称COW,是一种用于集合的并发访问的优化策略。基本思想是:当我们往一个集合容器中写入元素时(添加、修改、删除),并不会直接在集合容器中写入,而是先将当前集合容器进行Copy,复制出一个新的容器,然后新的容器里写入元素,写入操作完成之后,再将原容器的引用指向新的容器。好处:实现对CopyOnWrite集合容器写入操作时的线程安全,但同时并不影响进行并发的读取操作。所以CopyOnWrite容器也是一种读写分离的思想。
CopyOnWriteArrayList核心源码的阅读:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//ReentrantLock可重入锁
final transient ReentrantLock lock = new ReentrantLock();
//内部数组 array 存储数据,用 volatile 修饰,保证可见性,可以直接获取相应位置的对象。
private transient volatile Object[] array;
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
//返回array数组本身
final Object[] getArray() {
return array;
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
//在无参构造方法中会调用setArray方法,创建了一个长度为0的Object数组
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
//根据指定下标,到原数组中读取元素。读取过程中不加锁,允许多个线程并发读取。
//但是如果读取的时候,有其它线程向集合中添加新元素,此时仍然读取到的是旧数据。
//因为添加操作没有对原数组加锁。
public E get(int index) {
// 通过getArray()获取array属性值,根据指定下标,从原数组中读取元素
return get(getArray(), index);
}
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
// 上锁,同一时间内只能有一个线程进入
lock.lock();
try {
// 获取当前array属性值
Object[] elements = getArray();
// 获取当前array指定index下标值
E oldValue = get(elements, index);
// 如果新值和旧值不相等
if (oldValue != element) {
int len = elements.length;
// 复制一份新数组,长度和旧数组一致
Object[] newElements = Arrays.copyOf(elements, len);
// 修改新数组index下标值
newElements[index] = element;
// 新数组赋值给array属性,替换旧数组
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
// 即使新值和旧值一致,为了确保volatile语义,需要重新设置array
setArray(elements);
}
return oldValue;
} finally {
//释放锁
lock.unlock();
}
}
//添加新元素至集合时,会将当前数组Copy复制新数组,
//并将新元素添加至新数组,最后替换原数组。
//执行过程中,使用ReentrantLock加锁,保证线程安全,避免多个线程复制数组。
public boolean add(E e) {
//加ReentrantLock锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//得到原数组
Object[] elements = getArray();
int len = elements.length;
//复制原数组得到新数组,长度+1是因为要添加一个元素
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;
// 下标检查
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
// numMoved为0,说明是在末尾添加,过程和add(E e)方法一致
newElements = Arrays.copyOf(elements, len + 1);
else {
// 否则创建一个新数组,数组长度为旧数组长度值+1
newElements = new Object[len + 1];
// 分两次复制,分别将index之前和index+1之后的元素复制到新数组中
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
// 在新数组的index位置添加指定元素
newElements[index] = element;
// 新数组赋值给array属性,替换旧数组
setArray(newElements);
} finally {
lock.unlock();
}
}
//删除指定下标元素。根据指定下标,从原数组中,
//复制其它元素至新数组,最后替换原数组。
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)
// 如果要删除的是末端数据,则只用复制前面len-1个数据到新副本上,并切换引用
setArray(Arrays.copyOf(elements, len - 1));
else {
// 若删除的数据在数组中间:
// 设置新数组长度为旧数组长度-1,因为是减少一个元素
Object[] newElements = new Object[len - 1];
// 分段复制,将index前的元素和index+1后的元素复制到新数组
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
// 设置array
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);
//移动的范围是原数组长度-toIndex,将删除范围后的元素统一向前移动
int numMoved = len - toIndex;
//如果要移动的范围为0,说明删除仍是在末尾删,无需移动元素
if (numMoved == 0)
setArray(Arrays.copyOf(elements, newlen));//将原数组的newlen个长度复制给array数组
else {
//否则就是中间删除,需把下标toIndex后的元素向前移动
Object[] newElements = new Object[newlen];
//将原数组从0开始复制到新数组从0开始,复制fromIndex-1个结束
System.arraycopy(elements, 0, newElements, 0, fromIndex);
//将原数组从toIndex开始复制到新数组从fromIndex开始,复制numMoved-1个结束
System.arraycopy(elements, toIndex, newElements,
fromIndex, numMoved);
//设置array数组为新数组newElements
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
//newlen 表示新数组的索引位置,新数组中存在不包含在 c 中的元素
int newlen = 0;
//定义临时数组用来保存原数组
Object[] temp = new Object[len];
// 循环,把不包含在 c 里面的元素,放到新数组中
for (int i = 0; i < len; ++i) {
//拿到原数组中的每个元素
Object element = elements[i];
//若当前集合c不包含当前元素,说明该元素不用删除
if (!c.contains(element))
//将当前元素放在临时数组的长度+1位置
temp[newlen++] = element;
}
// 拷贝新数组,变相的删除了不包含在 c 中的元素
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
//清空数组
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//更新array数组为空数组
setArray(new Object[0]);
} finally {
lock.unlock();
}
}
//添加c中所有元素进数组
public boolean addAll(Collection<? extends E> c) {
//判断当前对象的Class类型是否等于CopyOnWriteArrayList的Class类型
//等于创建当前对象的数组给cs数组
//不等于则将c转换成array数组再赋给cs数组
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
//如果长度等于0,返回false
if (cs.length == 0)
return false;
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//若原数组长度为0且cs数组的Class对象等于Object数组的Class对象
if (len == 0 && cs.getClass() == Object[].class)
//设置array数组为cs
setArray(cs);
else {
//创建新数组,将原数组赋值给新数组并且长度加上cs数组的长度
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
//将cs数组从0开始复制到新数组从len开始,复制cs数组的长度-1个结束
System.arraycopy(cs, 0, newElements, len, cs.length);
//更新array数组为新数组
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);
//创建es数组指向新数组newElements
@SuppressWarnings("unchecked") E[] es = (E[])newElements;
//利用Array工具类的sort方法,按照自定义的比较器实现相应的排序
Arrays.sort(es, c);
//更新array数组为新数组
setArray(newElements);
} finally {
lock.unlock();
}
}
public boolean equals(Object o) {
//如果对象o等于当前对象返回true
if (o == this)
return true;
//如果对象o的类型不是List类型,返回false
if (!(o instanceof List))
return false;
//将对象o赋给list列表
List<?> list = (List<?>)(o);
//创建list的迭代器对象
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;
//遍历完数组后,如果当前集合还未遍历完毕,说明当前集合元素比原数组多,返回false
if (it.hasNext())
return false;
return true;
}
//计算哈希值
public int hashCode() {
//定义hashCode并赋值为1
int hashCode = 1;
//得到原数组
Object[] elements = getArray();
//得到原数组长度
int len = elements.length;
for (int i = 0; i < len; ++i) {
//得到数组中每个元素
Object obj = elements[i];
//计算当前元素的哈希值,当前元素为空,则返回0,否则返回哈希值
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
}
CopyOnWriteArrayList具有以下特性:
- 在保证并发读取的前提下,确保了写入时的线程安全;
- 由于每次写入操作时,进行了Copy复制原数组,所以无需扩容;
- 适合读多写少的应用场景。由于add()、set() 、 remove()等修改操作需要复制整个数组,所以会有内存开销大的问题。
- CopyOnWriteArrayList由于只在写入时加锁,所以只能保证数据的最终一致性,不能保证数据的实时一致性。