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只能保证数据最终一致性,不能保证数据的实时一致性。

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

posted on 2018-10-12 16:04  AoTuDeMan  阅读(337)  评论(0编辑  收藏  举报

导航