CopyOnWriteArrayList时ArrayList的一种线程安全变体,它的名字前面有个CopyOnWrite,这个名字就表明了它的操作特点,它的所有变动操作(新增add,设置set,删除remove等等)都是会先复制(copy)一个副本,然后再副本上进行相应操作,最后将副本写(write)回存储
CopyOnWriteArrayList的变动操作一般性的流程是:
1、加锁
2、复制底层的存储数组
3、在复制的数组上进行变动操作(add,set,remove等)
4、调用setArray方法,将复制出来的新数组进行存储并覆盖原数组
5、解锁
这种方式需要比较大的代价,但是当迭代操作的数量大大超过变更操作的数量时,可能会比其他方案更加有效,特别是当你不想或者不能对迭代操作进行同步,且要保证线程安全时,可以使用这种方案
关于这个类,我们先来看一看它的迭代操作
跟以前一样,定义了迭代器私有类来执行迭代操作,比较不一样的地方是,COWIterator在初始化的时候,会生成一个当前数组的快照,并且迭代操作就是在这个快照上进行的,所以和ArrayList不一样的地方是,在迭代过程中对数组的改动并不会影响迭代操作,并且该类保证永远不会抛出ConcurrentModificationException异常,毕竟是在快照上进行迭代,原本数组怎么变都没啥关系
static final class COWIterator<E> implements ListIterator<E> { /** 数组的快照 */ private final Object[] snapshot; /** 迭代索引,指向下一个要遍历的元素 */ private int cursor; COWIterator(Object[] es, int initialCursor) { cursor = initialCursor; snapshot = es; } }
该类的底层也是用一个对象数组进行存储,并且用了volatile关键字进行修饰
/** 锁对象 */ final transient Object lock = new Object(); /** 存储数据的底层数组,只能通过set/get方法访问 */ private transient volatile Object[] array;
底层数组的set和get,在进行变更操作时经常要用到
final Object[] getArray() { return array; } // 设置数组 final void setArray(Object[] a) { array = a; }
设置set操作
public E set(int index, E element) { // 加锁 synchronized (lock) { // 获取原数组 Object[] es = getArray(); // 获取原值 E oldValue = elementAt(es, index); // 如果值不相等,那么需要进行值的覆盖 if (oldValue != element) { // 对数组进行复制 es = es.clone(); // 在新数组上进行操作 es[index] = element; } // 将新数组写回 setArray(es); return oldValue; } }
增加add操作
public void add(int index, E element) { // 加锁同步 synchronized (lock) { Object[] es = getArray(); int len = es.length; // 判断索引是否越界 if (index > len || index < 0) throw new IndexOutOfBoundsException(outOfBounds(index, len)); // 构建新数组 Object[] newElements; // 判断添加的位置是否是数组的最后一个位置 int numMoved = len - index; // 如果是的话,复制一个长度为len+1的数组(因为要添加元素,所以长度要加1) if (numMoved == 0) newElements = Arrays.copyOf(es, len + 1); // 如果不是的话,新建len+1长度的数组 else { newElements = new Object[len + 1]; // 复制[0,index)位置的数据 System.arraycopy(es, 0, newElements, 0, index); // 复制[index+1,len)位置的数据 System.arraycopy(es, index, newElements, index + 1, numMoved); } // 将index位置上的数据设置为添加的数据 newElements[index] = element; // 将新建的数组写回 setArray(newElements); } }
该类中的读取操作并没有用到同步操作,和ArrayList类似
CopyOnWriteArraySet是一个底层使用了CopyOnWriteArraySet进行所有操作的set容器,因此它具有CopyOnWriteArrayList的属性
它比较适合于读操作远远大于写操作的应用程序,且需要在进行迭代操作的时候不会受到其他线程写数据的干扰
它主要使用addIfAbsent这个函数保证了数据的唯一性,在添加数据的时候,首先会读取一个数据的快照,然后在这个快照上查找是否有重复元素,如果没有元素重复,那么就会添加新元素
public boolean add(E e) { return al.addIfAbsent(e); } public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOfRange(e, snapshot, 0, snapshot.length) < 0 && addIfAbsent(e, snapshot); }
至于其他的一些操作,基本上就是调用CopyOnWriteArrayList的Api