Java集合003 --- CopyOnWriteArrayList
前言
CopyOnWriteArrayList实际上是ArrayList的线程安全版,内部实现也是数组,CopyOnWriteArrayList实现了RandomAccess、Cloneable、List、Serializable接口;
属性
// 可重入锁, 可重入的概念是: 当某个线程获取到锁之后, 该线程还能获取到该锁 // 不自动序列化 final transient ReentrantLock lock = new ReentrantLock(); // 数组, 不自动序列化、修改对其他线程可见并且禁止指令重排 private transient volatile Object[] array;
可以看到,CopyOnWriteArrayList与ArrayList的区别是:CopyOnWriteArrayList无数据实际个数size,这是为啥呢?
这是因为CopyOnWriteArrayList在写元素时,使用新的数组,然后赋值,因此数组的长度既是元素的个数
构造方法
final void setArray(Object[] a) { array = a; } public CopyOnWriteArrayList() { setArray(new Object[0]); // 创建容量为0的数组 } public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) // 类型为CopyOnWriteArrayList, 强转然后获取数组 elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { // 和ArrayList类似, 存在元素类型不为Object的场景 elements = c.toArray(); if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); } public CopyOnWriteArrayList(E[] toCopyIn) { // 直接使用数组 setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
添加单个元素
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); // 加锁 try { Object[] elements = getArray(); // 获取当前数组 int len = elements.length; // 创建长度为len+1的新数组, 并且前len个元素为老数组的元素 Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; // 在新数组的最后添加元素 setArray(newElements); // 更新list数组 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) // index合法性检查 throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len); Object[] newElements; int numMoved = len - index; // 判断是不是在最后添加元素 // 看了这几个list源码, 都是这样处理的; // 我写代码的思路是:所有的场景复用同一个流程, 看到此处源码, 感受颇多 if (numMoved == 0) // 创建长度为len+1的新数组, 并且前len个元素为老数组的元素 newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; // 先将原数组0~index-1个元素拷贝到新数组 System.arraycopy(elements, 0, newElements, 0, index); // 将原数组index~end个元素拷贝到新数组index+1~end System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; // 将新数组的第index位置赋值 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; // 获取指定索引元素, 如果元素不存在, 抛ArrayIndexOutOfBoundsException // 而不是IndexOutOfBoundsException异常 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]; // 拷贝原数组0~index-1元素到新数组 System.arraycopy(elements, 0, newElements, 0, index); // 拷贝原数组index+1~len元素到新数组 System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements);// 更新数组 } return oldValue;// 返回index处的取值 } finally { lock.unlock();//释放锁 } }
获取指定索引的元素
public E get(int index) { // 不校验index, 数组越界抛ArrayIndexOutOfBoundsException return get(getArray(), index); } @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; }
如果元素不存在则添加元素(返回值:元素存在返回false;元素不存在返回true)
public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); // 存在返回false; 不存在添加元素 return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } // 由于原数组是作为入参传入到该方法的, 此处也没有获取锁, 因此存在原数组被修改的情况 private boolean addIfAbsent(E e, Object[] snapshot) { final ReentrantLock lock = this.lock; lock.lock();// 获取锁 try { Object[] current = getArray(); int len = current.length; if (snapshot != current) { // 原数组被修改 int common = Math.min(snapshot.length, len); for (int i = 0; i < common; i++) if (current[i] != snapshot[i] && eq(e, current[i])) return false; if (indexOf(e, current, common, len) >= 0) return false; } // 在最后添加元素 Object[] newElements = Arrays.copyOf(current, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); // 释放锁 } }
对于集合来说,IfAbsent、IfPresent是很常见的方法,我在理解这块的功能的方法是:Absent是缺席的意思,AddIfAbsent的意思就是如果元素不存在则添加元素
序列化和反序列化
和ArrayList相似,都是采用按需序列化的方法
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { s.defaultWriteObject(); Object[] elements = getArray(); // Write out array length s.writeInt(elements.length); // Write out all elements in the proper order. for (Object element : elements) s.writeObject(element); } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // bind to new lock resetLock(); // Read in array length and allocate array int len = s.readInt(); Object[] elements = new Object[len]; // Read in all elements in the proper order. for (int i = 0; i < len; i++) elements[i] = s.readObject(); setArray(elements); }
总结
1、CopyOnWriteArrayList内部由数组实现,线程安全
2、CopyOnWriteArrayList在写的时候,创建新的数组,写的时候内存占用高
3、CopyOnWriteArrayList适用读多写少的场景
4、CopyOnWriteArrayList能保证一致性,但是不能保证实时一致性
心有猛虎,细嗅蔷薇