CopyOnWriteArrayList
- 特点
- 线程安全的,多线程直接使用,无须加锁
- 通过锁+数组拷贝+volatile关键字保证了线程安全
- 每次数组操作,都会把数组拷贝一份出来,在新数组上进行操作,操作完成之后再赋值回去
- 整体步骤:对数组进行操作的时候
- 加锁
- 从原数组拷贝出新数组
- 在新数组上进行操作,并把新数组赋值给数组容器
1. 继承体系
- 代码
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { ... }
- RandomAccess接口,是一个标志接口,表明实现这个接口的List集合是支持快速随机访问的,同时,在jdk源码中,还说明了,此注解表示的类,使用for通过索引访问的速度要比使用迭代器使用要快一点,建议使用for+索引的方式来进行查询访问
2. 具体方法
2.1 新增
- 代码
- 添加到尾部
public boolean add(E e) { final ReentrantLock lock = this.lock; //加锁 lock.lock(); try { //1, 得到原来是数组 Object[] elements = getArray(); int len = elements.length; //2. 拷贝的新的数组 Object[] newElements = Arrays.copyOf(elements, len + 1); //3. 将新的元素添加到新的数组中 newElements[len] = e; //4. 替换旧的数组 setArray(newElements); return true; } finally { //解锁 lock.unlock(); } }
- 在内部任意位置增加
public void add(int index, E element) { final ReentrantLock lock = this.lock; //1. 加锁 lock.lock(); try { //2. 获取之前的数组 Object[] elements = getArray(); int len = elements.length; if (index > len || index < 0) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len); Object[] newElements; //3. 计算出需要移动的元素个数 int numMoved = len - index; //4. 正好是最后一位 if (numMoved == 0) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; //将原数组的[0,index)移动到新数组中,index为移动的个数 System.arraycopy(elements, 0, newElements, 0, index); //将原数组的[index,length)数组移动到新的数组中,其中numMoved为个数 System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }
- 设置数组
final void setArray(Object[] a) { array = a; }
- 注意点
- 总体的流程为
- 使用lock进行枷锁
- 获取旧是数组
- 生成新的数组,将旧的数组复制到新的数组
- 将底层的数组设置为新的数组
- 数组为volatile修改,为的是修改数组引用时,不同的线程可以得知变化,刷新本地缓冲。
2.2 删除
- 代码
1.删除指定数组索引:可以看出和新增的逻辑是相同的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]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; } 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; } if (newlen != len) { //将temp临时数组赋值到底层的数组 setArray(Arrays.copyOf(temp, newlen)); return true; } } return false; } finally { lock.unlock(); } }
- 注意点
- 对于批量删除,使用了空间换时间的操作,将不需要删除的元素保存到一个临时数组中,最后判断是否删除了元素,如果个数不同,将临时数组当成底层数组实现了批量删除操作
2.3 迭代器遍历
- 代码
- 迭代器遍历
public Iterator<E> iterator() { return new COWIterator<E>(getArray(), 0); } static final class COWIterator<E> implements ListIterator<E> { //保存当前需要迭代的数组,使用final赋值,表示,一旦赋值就不能改变 private final Object[] snapshot; private int cursor; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } }
- 注意点
- 即使CopyOnWriteArrayList在迭代过程中,发生了变动了,也不会抛出ConcurrentModificationException异常,因为每次迭代的时候都是使用的旧的数组,不会遍历到新数组的元素。