1、CopyOnWriteArrayList并发容器java
1.CopyOnWriteArrayList的底层数据结构数组
CopyOnWriteArrayList的底层实现是数组,CopyOnWriteArrayList是ArrayList的一个线程安全的变体,其增删改操做都是经过对底层数组的从新复制来实现的。这种容器内存开销很大,可是在读操做频繁(查询),写操做(增删改)极少的场合却极用,而且每一次写操做都是在一块新的空间上进行的,不存在并发问题。安全
2.CopyOnWriteArrayList的继承关系数据结构
CopyOnWriteArrayList继承关系以下图所示,List接口和Collection接口在学习ArrayList时已经分析过,不在多说。并发
3.重要属性和构造方法dom
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { //可重入锁 final transient ReentrantLock lock = new ReentrantLock(); //底层实现,用于存放数据的数组 private transient volatile Object[] array; //获取数组 final Object[] getArray() { return array; } //修改数组 final void setArray(Object[] a) { array = a; } //默认构造器,初始化一个长度为0 的数组 public CopyOnWriteArrayList() { setArray(new Object[0]); } //将集合c中全部元素转化成数组的形式,存储与当前List中 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); } //将数组toCopyIn拷贝以一份到array中 public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); } }
4.add与set过程学习
CopyOnWriteArrayList在新增元素时,会先进行加锁操做,以保证写操做的并发安全ui
//将新增数据加到数组末尾 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]; //将原数组中0到index的元素复制到新数组的0到index位置 System.arraycopy(elements, 0, newElements, 0, index); //将原数组的index以后的数据复制到新数组的index+1位置以后 System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; //新增element setArray(newElements); } finally { lock.unlock(); } } //修改index索引上的数据 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(); } }
5.get的过程this
CopyOnWriteArrayList的读取过程并没加锁,这是由于CopyOnWriteArrayList的读过程是自然线程安全的,全部的写操做都是在一块新的内存空间上,而读操做则是在原有的空间上进行的,不会出现并发问题,读写自然分离。线程
public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }
6.remove的过程
删除的过程其实以新增修改同样,都是新建数组实现的。
//根据索引删除数据 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(); } } //将o从数组中删除 public boolean remove(Object o) { Object[] snapshot = getArray(); int index = indexOf(o, snapshot, 0, snapshot.length); //获取o的索引位置 //判断index是否合法 return (index < 0) ? false : remove(o, snapshot, index); } //将snapshot中index位置的数据o删除 private boolean remove(Object o, Object[] snapshot, int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); //获取当前数组 int len = current.length; //判断数组是否发生改变,便是否有其余线程将数组修改了 if (snapshot != current) findIndex: { //比较修改后的数组长度与index索引的大小,取小的值 int prefix = Math.min(index, len); //遍历current数组查找第一个与快照数组元素不一样的索引 for (int i = 0; i < prefix; i++) { if (current[i] != snapshot[i] && eq(o, current[i])) { index = i; break findIndex; } } //o元素索引大于等于current数组的长度 //说明current数组中不存在o元素 if (index >= len) return false; //o元素的索引仍在current数组中 //且要删除元素的索引的没有发生改变 if (current[index] == o) break findIndex; //o元素索引起生改变,从新获取o元素的索引 index = indexOf(o, current, index, len); if (index < 0) return false; } Object[] newElements = new Object[len - 1]; System.arraycopy(current, 0, newElements, 0, index); System.arraycopy(current, index + 1, newElements, index, len - index - 1); setArray(newElements); return true; } finally { lock.unlock(); } }
7.总结
由上面的分析可得出CopyOnWriteArrayList具备以下特色:
1.CopyOnWriteArrayList是线程安全的容器,读写分离,写数据时经过ReentrantLock锁定一个线程拷贝数组元素进行增删改操做;读数据时则不须要同步控制。
2.CopyOnWriteArrayList中是容许null数据和重复数据的
3.CopyOnWriteArrayList的并发安全性是经过建立新的数组和重入锁实现的,会耗费大量内存空间,只适合读多谢写少,数据量不大的环境。
2、CopyOnWriteArraySet并发容器
1.CopyOnWriteArraySet
因为CopyOnWriteArraySet的底层是经过CopyOnWriteArrayList来实现的,其特色与CopyOnWriteArrayList,所以再也不作过多的分析。