zoukankan      html  css  js  c++  java
  • CopyOnWriteArrayList

    CopyOnWriteArrayList 是一个线程安全,读无锁写时复制的ArrayList。

    CopyOnWriteArrayList 是典型的空间换时间方式。

    写时复制:当新元素添加到CopyOnWriteArrayList时,会先把原来数组的元素拷贝到新的数组中,然后在新的数组中做写操作,写操作完成之后,再将原来的数组引用(volatile修饰的数组引用)指向新的数组。

    CopyOnWriteArrayList 的几个重要的方法:

      add(E e):添加元素到末尾。

      add(int index, E element):添加元素到指定位置。

      get(int index):获取指定位置上的元素。

      remove(int index):删除指定位置上的元素,返回该元素的值。

      remove(Object o):删除元素o,成功返回true,失败返回false。

      遍历CopyOnWriteArrayList:iterator()遍历,实际中更常用的是 foreach 循环遍历。

    下面通过源码来更加深入的了解CopyOnWriteArrayList:

    CopyOnWriteArrayList的类的声明

    public class CopyOnWriteArrayList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        ......
    }

      说明:CopyOnWriteArrayList实现了List接口,List接口定义了对列表的基本操作;同时实现了RandomAccess接口,表示可以随机访问(数组具有随机访问的特性);同时实现了Cloneable接口,表示可克隆;同时也实现了Serializable接口,表示可被序列化。

    CopyOnWriteArrayList类变量的声明

        transient final ReentrantLock lock = new ReentrantLock();
        private volatile transient Object[] array;

      说明:属性中有一个可ReentrantLock重入锁,用来保证线程安全访问,还有一个被关键字volatile修饰的Object类型的数组,用来存放具体的元素,这个非常重要,保证了线程对数组改变的可见性,即线程每次获取到的都是最新的数组。

    add(E e):

     1 public boolean add(E e) {
     2         // 可重入锁
     3         final ReentrantLock lock = this.lock;
     4         // 获取锁
     5         lock.lock();
     6         try {
     7             // 元素数组
     8             Object[] elements = getArray();
     9             // 数组长度
    10             int len = elements.length;
    11             // 复制数组
    12             Object[] newElements = Arrays.copyOf(elements, len + 1);
    13             // 存放元素e
    14             newElements[len] = e;
    15             // 设置数组
    16             setArray(newElements);
    17             return true;
    18         } finally {
    19             // 释放锁
    20             lock.unlock();
    21         }
    22     }

    说明:此函数用于将指定元素添加到此列表的尾部,处理流程如下

      ❤ 代码的第3行获取锁(保证多线程的安全访问),代码的第8行获取当前的Object数组,代码的第10行获取Object数组的长度为length;

      ❤ 代码的第12行根据Object数组复制一个长度为length+1的Object数组为newElements(此时,newElements[length]为null);

      ❤ 代码第14行将下标为length的数组元素newElements[length]设置为元素e,代码第16行再设置当前Object[ ]为newElements,代码第20行释放锁,返回。

     这样就完成了元素的添加。

     可以看出CopyOnWriteArrayList的写操作,首先需要获取lock锁对象,其次只要对CopyOnWriteArrayList进行写,修改,删除操作时,都会伴随着Arrays.copyOf()拷贝操作,这些原因导致了CopyOnWriteArrayList写操作的性能低下。

     遍历CopyOnWriteArrayList:

    以iterator()为例,对CopyOnWriteArrayList的遍历操作”进行说明:

    public Iterator<E> iterator() {
            return new COWIterator<E>(getArray(), 0);
        }
     1 static final class COWIterator<E> implements ListIterator<E> {
     2         /** Snapshot(快照) of the array */
     3         private final Object[] snapshot;
     4         /** Index of element to be returned by subsequent call to next.  */
     5         private int cursor;    // 游标
     6 
     7         private COWIterator(Object[] elements, int initialCursor) {
     8             cursor = initialCursor;
     9             snapshot = elements;
    10         }
    11 
    12         public boolean hasNext() {
    13             return cursor < snapshot.length;
    14         }
    15 
    16         public boolean hasPrevious() {
    17             return cursor > 0;
    18         }
    19 
    20         // 获取下一个元素
    21         @SuppressWarnings("unchecked")
    22         public E next() {
    23             if (! hasNext())
    24                 throw new NoSuchElementException();
    25             return (E) snapshot[cursor++];
    26         }
    27 
    28         // 获取上一个元素
    29         @SuppressWarnings("unchecked")
    30         public E previous() {
    31             if (! hasPrevious())
    32                 throw new NoSuchElementException();
    33             return (E) snapshot[--cursor];
    34         }
    35 
    36         public int nextIndex() {
    37             return cursor;
    38         }
    39 
    40         public int previousIndex() {
    41             return cursor-1;
    42         }
    43 
    44 
    45         //不支持remove
    46         public void remove() {
    47             throw new UnsupportedOperationException();
    48         }
    49 
    50         //不支持set
    51         public void set(E e) {
    52             throw new UnsupportedOperationException();
    53         }
    54 
    55 
    56         //不支持add
    57         public void add(E e) {
    58             throw new UnsupportedOperationException();
    59         }
    60 
    61         @Override
    62         public void forEachRemaining(Consumer<? super E> action) {
    63             Objects.requireNonNull(action);
    64             Object[] elements = snapshot;
    65             final int size = elements.length;
    66             for (int i = cursor; i < size; i++) {
    67                 @SuppressWarnings("unchecked") E e = (E) elements[i];
    68                 action.accept(e);
    69             }
    70             cursor = size;
    71         }
    72     }

    说明:

      (1)创建迭代器的时候, 会保存数组元素的快照(有一个引用指向原数组),并发情况下可能会导致遍历与实际的结果不一致,所以在迭代的过程中,往CopyOnWriteArrayList中添加删除元素都不会抛出异常,连感知都感知不到,因为操作的底层数组都是不一样的。

      (2)COWIterator不支持修改元素的操作。例如,对于remove(), set(), add()等操作,COWIterator都会抛出异常!

      (3)另外,需要提到的一点是,CopyOnWriteArrayList返回迭代器不会抛出ConcurrentModificationException异常,即它不是fail-fast机制的!

    CopyOnWriteArrayList 的缺点:

      ❤ 内存占用问题,在写操作时,由于会拷贝数组,若原数组较大,则有可能会导致Full GC频繁,且GC响应时间较长。

      ❤ 数据一致性问题,CopyOnWriteArrayList只能保证数据最终一致性,不能保证数据的实时一致性。

      ❤ 只适合读多写少的场景。

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    案例分析
    阅读任务
    准备工作
    课程总结
    十三周总结
    第十二周总结
    第十一周学习总结
    第十周学习总结
    第九周课程总结&实验报告7
    第八周课程学习总结与实验6
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/9778699.html
Copyright © 2011-2022 走看看