◆
CopyOnWriteArrayList简介
◆
CopyOnWriteArrayList和ArrayList一样是一个动态数组。而与ArrayList不同的是,它是线程安全的。
建议先阅读 ArrayList源码分析 ,再回来看此文会Soeasy哦!
CopyOnWriteArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
◆
CopyOnWriteArrayList的属性
◆
/**实现线程安全的锁对象*/ final transient ReentrantLock lock = new ReentrantLock();
/** 元素缓冲区,volatile保证多线程下始终读取到最新的数据*/ private transient volatile Object[] array;
上方的lock对象就是CopyOnWriteArrayList实现线程安全的秘诀。
对于Java中的锁有疑问的同学可以参考此文章: Java中的锁
◆
CopyOnWriteArrayList的构造方法
◆
public CopyOnWriteArrayList() { setArray(new Object[0]); }
public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == java.util.concurrent.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)); } final void setArray(Object[] a) { array = a; }
三个构造方法最后都是调用的setArray方法完成的初始化。
◆
CopyOnWriteArrayList的方法
◆
接下来我们就以CopyOnWriteArrayList的几个比较经典的方法来看一下它是如何设计的。
首先是添加方法:
/** * 添加 */ 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(); } }
/** * 指定索引添加 */ 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) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } } final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; }
可以看的,进行添加之前首先是要先获得锁对象才继续进行的操作(包括修改和删除),只有这样才能保证线程安全。
接着看添加的逻辑
新建一个数组,接着将通过getArray()方法获取到的原始的数组拷贝到新数组中,然后将新增数据也添加到新数组中;最后将新数组赋值给原先的数组。
接下来看删除操作:
/** * 删除 */ 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(); } }
同添加方法相同的逻辑,先获取锁,然后通过copy数组方式进行删除操作。
接下来修改方法,修改的时候也使用到了查询方法:
/** * 替换指定索引的元素 */ 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(); } } /** * get方法 */ public E get(int index) { return get(getArray(), index); } @SuppressWarnings("unchecked") private E get(Object[] a, int index) { return (E) a[index]; }
第一步同样是加锁,接着会有一个判断,看要修改的索引位置的元素是否相同,不相同则继续通过copy数组的方式进行替换,最后使用setArray()方法更新。
看了CopyOnWriteArrayList的增删改查方法你就应该明白一件事,这哥们除了查询、增删改都很慢呀。
与ArrayList相比,CopyOnWriteArrayList最值得我们注意的地方就是:
鉴于篇幅有限,本篇文章仅列出上方部分代码,CopyOnWriteArrayList完整源码解析请点击下方“阅读原文”查看!!!