CopyOnWriteArrayList源码详解

来源:互联网 发布:淘宝宝贝像素 编辑:程序博客网 时间:2024/06/04 22:16

1. CopyOnWriteArrayList概述

CopyOnWriteArrayList是线程安全集合,它内部通过一个数组存储元素。通过名字可以看出,这是一个“写时复制”的List,每次要修改该List的时候,都会new一个新的数组,copy原来数组元素到该新的数组,并在该新的数组上修改,修改完后,更新原有数组引用到该新的数组。

读的时候不需要加锁,读操作都是在原来数组上进行的,多线程可以并发的读。但是写是需要加锁的,否则多线程写会创建N个数组。读操作在原有数组进行,写操作在新的数组上进行,互不干扰,读写分离。

“写时复制”会带来很大性能问题,当写操作比较多时,每次修改都将new一个新的数组并将原来数组元素copy到该新的数组,效率较低,因此,CopyOnWriteArrayList适合读远多于写的场景。

看下CopyOnWriteArrayList类图:

这里写图片描述

CopyOnWriteArrayList实现了List、RandomAccess、Cloneable、Serializable接口。内部通过数组存储元素,因此它是一个“随机存取”的List。RandomAccess是一个空的接口,实现它仅仅标示CopyOnWriteArrayList是一个“随机存取”的List。

3. 构造方法

CopyOnWriteArrayList有三个构造方法,看下源码:

//“写”操作加锁final transient ReentrantLock lock = new ReentrantLock();//内部存储元素的数组private transient volatile Object[] array;//该方法和下面的setArray方法都是包访问权限,用户代码无法访问该方法,因此无法直接修改内部数组//并且这两个方法都是final,无法被子类重写,避免数组被子类修改final Object[] getArray() {    return array;}final void setArray(Object[] a) {    array = a;}//创建一个空的Listpublic CopyOnWriteArrayList() {    setArray(new Object[0]);}//创建的List包括指定集合的元素public CopyOnWriteArrayList(Collection<? extends E> c) {    Object[] elements;    //如果指定集合也是一个CopyOnWriteArrayList,将引用指向该集合的内部数组即可    if (c.getClass() == CopyOnWriteArrayList.class)      elements = ((CopyOnWriteArrayList<?>)c).getArray();    else {      elements = c.toArray();      if (elements.getClass() != Object[].class)        elements = Arrays.copyOf(elements, elements.length, Object[].class);    }    //设置引用    setArray(elements);}//通过指定数组初始化CopyOnWriteArrayList,不能直接引用该数组,因为该数组可以被随意修改,//必须重新创建一个数组并从指定的数组copy元素到新的数组public CopyOnWriteArrayList(E[] toCopyIn) {    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));}

CopyOnWriteArrayList通过可重入锁ReentantLock,对写操作加锁,通过Object数组实现内部存储。

通过指定数组初始化时必须创建一个新的数组,而不能直接将数组引用指向参数数组,因为该参数数组可以被外部随意修改。

2. 增加方法

add方法

CopyOnWriteArrayList有很多增加元素的方法,这些方法都是“写”方法,修改之前需要加锁,首先看最简单的add方法。

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();    }}

修改元素之前通过Arrays.copyOf方法拷贝一份数组,并在新的数组上修改,修改完后设置数组引用为新建的数组。

CopyOnWriteArrayList还有另外一个在指定位置增加元素的方法,思路基本相同,不再介绍。

接着看下addAll方法

addAll方法

该方法将指定集合的元素都添加到该CopyOnWriteArrayList,看下源码。

public boolean addAll(Collection<? extends E> c) {    Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?      ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();    if (cs.length == 0)      return false;    final ReentrantLock lock = this.lock;    //写操作要加锁!    lock.lock();    try {      Object[] elements = getArray();      int len = elements.length;      //如果本数组大小为0,将数组引用指向cs      if (len == 0 && cs.getClass() == Object[].class)        setArray(cs);      else {        //写时复制!        //先将elements数组拷贝到一个更大的数组,然后将cs中的元素也拷贝到该更大的数组        Object[] newElements = Arrays.copyOf(elements, len + cs.length);        System.arraycopy(cs, 0, newElements, len, cs.length);        setArray(newElements);      }      return true;    } finally {      //释放锁      lock.unlock();    }}

方法很简单,关键点看注释即可。

还有一个在指定位置插入指定集合元素的方法,思路基本相同,不再讲解。

addAllAbsent

该方法有一个集合参数c,将c中不存在于该CopyOnWriteArrayList的元素加到该CopyOnWriteArrayList中。看下源码:

public int addAllAbsent(Collection<? extends E> c) {    Object[] cs = c.toArray();    if (cs.length == 0)      return 0;    final ReentrantLock lock = this.lock;    //写操作必须加锁    lock.lock();    try {      Object[] elements = getArray();      int len = elements.length;      int added = 0;      //这个for循环将c中不存在于该CopyOnWriteArrayList的元素放到cs前面部分      for (int i = 0; i < cs.length; ++i) {        Object e = cs[i];        //既不存在于当前CopyOnWriteArrayList的数组,也不存在于cs数组中        if (indexOf(e, elements, 0, len) < 0 &&            indexOf(e, cs, 0, added) < 0)          cs[added++] = e;      }      //一次性将元素添加到CopyOnWriteArrayList的数组中      if (added > 0) {        Object[] newElements = Arrays.copyOf(elements, len + added);        System.arraycopy(cs, 0, newElements, len, added);        setArray(newElements);      }      return added;    } finally {      //释放锁      lock.unlock();    }}

addAllAbsent将最终需要添加到该CopyOnWriteArrayList的元素放到一个数组中,然后一次性添加。

3. 删除方法

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;      //numMoved=0,说明删除的是数组最后一个元素      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 remove(Object o) {    Object[] snapshot = getArray();    //找到元素o的位置    int index = indexOf(o, snapshot, 0, snapshot.length);    return (index < 0) ? false : remove(o, snapshot, index);}

该方法删除指定的元素,而不是根据索引删除,首先调用indexOf方法找到该元素在数组中的索引,然后调用另一个重载的删除方法删除该元素。

removeAll

该方法有一个指定集合的参数,删除本List中所有存在于指定集合中的元素。看下源码:

//删除所有存在于集合c中的元素,返回该List是否改变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) {        int newlen = 0;        Object[] temp = new Object[len];        for (int i = 0; i < len; ++i) {          Object element = elements[i];          //说明当前元素不在指定的集合c中,不需要删除,放在临时数组中          if (!c.contains(element))            temp[newlen++] = element;        }        //数组有改变,copy一份临时数组并将引用设置为该数组        if (newlen != len) {          setArray(Arrays.copyOf(temp, newlen));          return true;        }      }      //未改变      return false;    } finally {      lock.unlock();    }}

该方法的实现思路也很简单,将存在于指定集合的元素放在一个临时数组,最后copy一份该临时数组,重新设置CopyOnWriteArrayList的数组引用。

CopyOnWriteArrayList还有一个删除指定范围内元素的方法,不再讲解。

4. 迭代器

CopyOnWriteArrayList内部静态类COWIterator实现了迭代器,需要注意的是,这里的迭代器凡是修改操作都会抛出UnsupportedOperationException,即不能通过迭代器修改。看下源码:

static final class COWIterator<E> implements ListIterator<E> {    //原数组的一个快照    private final Object[] snapshot;    //下一个元素的索引    private int cursor;    private COWIterator(Object[] elements, int initialCursor) {      cursor = initialCursor;      snapshot = elements;    }    public boolean hasNext() {      return cursor < snapshot.length;    }    public boolean hasPrevious() {      return cursor > 0;    }    @SuppressWarnings("unchecked")    public E next() {      if (! hasNext())        throw new NoSuchElementException();      return (E) snapshot[cursor++];    }    @SuppressWarnings("unchecked")    public E previous() {      if (! hasPrevious())        throw new NoSuchElementException();      return (E) snapshot[--cursor];    }    public int nextIndex() {      return cursor;    }    public int previousIndex() {      return cursor-1;    }    //下面几个修改操作都抛出异常    public void remove() {      throw new UnsupportedOperationException();    }    public void set(E e) {      throw new UnsupportedOperationException();    }    public void add(E e) {      throw new UnsupportedOperationException();    }    //函数式编程的思想,遍历数组并处理该元素    @Override    public void forEachRemaining(Consumer<? super E> action) {      Objects.requireNonNull(action);      Object[] elements = snapshot;      final int size = elements.length;      for (int i = cursor; i < size; i++) {        @SuppressWarnings("unchecked") E e = (E) elements[i];        //处理该元素        action.accept(e);      }      cursor = size;    }}

这个迭代器只能查看而不能做任务修改。注意forEachRemaining方法,该方法遍历数组元素并处理该元素,关于函数式编程,以后有机会再好好学习下。

原创粉丝点击