今天我们来学习一下CopyOnWriteArrayList这个比ArrayList更安全的集合,同时带着以下几个问题去分析源码。
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { //...... } 复制代码
首先我们来看一下CopyOnWriteArrayList的继承关系,从源码中可以看出它分别实现了List、RandomAccess、Cloneable以及Serializable,对于我们比较熟悉的应该是List和Serializable了。
//可以看出这是重入锁 final transient ReentrantLock lock = new ReentrantLock(); //底层数组,可以看出用了volatile private transient volatile Object[] array; 复制代码
通过以上两个核心字段,可以猜出CopyOnWriteArrayList内部进行了加锁来保证线程安全。那么它到底是如何实现的呢?我们继续往下分析。
/** * 默认构造方法,初始化了一个空数组 */ public CopyOnWriteArrayList() { setArray(new Object[0]); } 复制代码
再来简单看一下其他两个构造方法,如下:
public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<?>)c).getArray(); else { elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) 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 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; //设置array为newElements setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { //解锁 lock.unlock(); } } 复制代码
可以看出在写入数据时通过加锁来保证线程安全,并且通过复制一个新数组。然后修改新数组中index对应的Value值,最后将新数组赋值给CopyOnWriteArrayList的底层数组array,也可以说是将array数组指向新数组,如下图:
对于CopyOnWriteArrayList的set()方法,我们可以这样理解。
public boolean add(E e) { 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(); } } 复制代码
可以看出与set()方法类似,也是通过加锁的方式来保证线程安全。然后创建一个新数组来添加元素,最后将新数组赋值给array。
public E get(int index) { return get(getArray(), index); } @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; } 复制代码
从get()方法源码中可以看出 ,在从CopyOnWriteArrayList获取元素时并没有进行加锁。那么我们就要思考一个问题了,如果当写入数据时,在没有写完数据的情况下。再去读取数据,那么我们能准确的获得数据吗?而且重点是在get()方法中,是直接对底层数组array进行操作的。那就意味着在我们读取数据时,有可能获得的是旧数据。也就是说CopyOnWriteArrayList并不能保证数据的实时性,主要原因可以从以下几点来思考。
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(); } } 复制代码
可以看出remove()方法也是一样的,通过加锁来保证线程安全。
/** * 获取底层数组 */ final Object[] getArray() { return array; } /** * 设置底层数组 */ final void setArray(Object[] a) { array = a; } /** * 获取数组大小 */ public int size() { return getArray().length; } /** * 判断数组是否为空 */ public boolean isEmpty() { return size() == 0; } 复制代码
通过查看源码,我们可以看出CopyOnWriteArrayList最大的优点就是线程安全,也是最突出的优点。它在读取操作时没有加锁,只有当进行写入操作时才会加锁。这也体现出了CopyOnWriteArrayList的一个缺点,那就是无法读取实时数据。因为可能当写操作还没有完成时,就进行了读取操作,那么读取的将是旧数据。
接下来我们来简单了解一下CopyOnWriteArrayList的表兄弟CopyOnWriteArraySet,主要源码如下。
private final CopyOnWriteArrayList<E> al; public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } 复制代码
通过构造方法就可以看出初始化了一个CopyOnWriteArrayList,也就是说CopyOnWriteArraySet内部是用CopyOnWriteArrayList来实现的,我们再来看一下其他几个方法。
public boolean add(E e) { return al.addIfAbsent(e); } public boolean remove(Object o) { return al.remove(o); } 复制代码
可以看出add()和remove()都是通过CopyOnWriteArrayList来实现。
通过分析源码,我们了解了CopyOnWriteArrayList的内部实现,以及它是如何保证线程安全的,还了解一下CopyOnWriteArraySet。