CopyOnWriteArrayList源码详解

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

//创建一个空的List
public 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方法,该方法遍历数组元素并处理该元素,关于函数式编程,以后有机会再好好学习下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CopyOnWriteArrayList是一个线程安全的List实现,它通过每次修改操作(添加、删除、修改)时都创建一个新的底层数组来实现线程安全性。 CopyOnWriteArrayList的特点如下: 1. 线程安全:多个线程可以同时读取CopyOnWriteArrayList的内容,而不需要额外的同步机制。这使得它非常适合在读操作远远多于写操作的场景中使用。 2. 写操作的代价较高:每次对CopyOnWriteArrayList进行写操作时,都会创建一个新的底层数组,因此写操作的代价较高。 3. 实时性较低:由于写操作会创建新的底层数组,读取操作可能会看到旧的数据,因此CopyOnWriteArrayList的实时性较低。 使用CopyOnWriteArrayList的示例代码如下: ```java import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("Hello"); list.add("World"); for (String item : list) { System.out.println(item); } } } ``` 在上述代码中,我们创建了一个CopyOnWriteArrayList,并向其中添加了两个元素。然后使用增强for循环遍历CopyOnWriteArrayList中的元素,并打印输出。 需要注意的是,CopyOnWriteArrayList适用于读操作远远多于写操作的场景,如果写操作非常频繁,可能会导致性能问题。此外,CopyOnWriteArrayList不保证元素的顺序性,因为在写操作时会创建新的底层数组。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值