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只能保证数据最终一致性,不能保证数据的实时一致性。
❤ 只适合读多写少的场景。