java源码阅读——CopyOnWriteArrayList和CopyOnWriteArraySet
作者:互联网
目录
CopyOnWriteArrayList
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是一个底层使用了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
标签:index,java,迭代,Object,CopyOnWriteArrayList,源码,数组,操作,es 来源: https://blog.csdn.net/Campsisgrandiflora/article/details/118997256