探索CopyOnWriteArrayList底层实现
简单介绍
由于CopyOnWriteArrayList的注释并不是很多,所以在这里简单的说明下,它属于线程安全,底层是通过生成数组的新副本来实现的,也就是在修改列表元素/结构的情况会生成新副本。简单地说,它是ArrayList的一个变体!探索CopyOnWriteArrayList源代码是基于JDK1.8版本的。
开干
开始进入到看源码的时间吧!
数据结构
//支持随机访问,可克隆,可序列化
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//序列号
private static final long serialVersionUID = 8673264195747942595L;
//目前先知道是锁就行了,后续会有新文章来进行详细说明
//保证同一个时间内只有一个线程能访问,
final transient ReentrantLock lock = new ReentrantLock();
//保证变量的可见性,但无法保证原子性
//至于什么是可见性、原子性,较为难理解,况且也不是本章的重点,加上作者对其的理解还不够,所以后续才会出文章去做详细说明
//该数组用于存放元素
private transient volatile Object[] array;
//相比于ArrayList,为什么没有了size、modCount成员属性呢?
//因为每次添加/删除元素时,都会生成数组的新副本,也就是说新副本代替了size的作用
//modCount在ArrayList中主要用于在迭代器的结构修改判断中,而CopyOnWriteArrayList的迭代器中不支持结构修改,为什么不支持呢?
//源码中并未提到为什么不支持,以下主要是自我的理解
//结构修改中必然涉及到加锁,若对迭代器加锁了,要是对它进行遍历上千条数据,那其他线程就不用执行了,所以迭代器万不可加锁!
//不能加锁,那对于结构的修改势必会造成并发访问的问题,所以目前是没有提供支持,纯属个人理解!
//JDK1.8并未有该类的相关注释,偏向底层,目前作者只知道它是跟锁有关联的,若想知道可自行百度
private static final sun.misc.Unsafe UNSAFE;
private static final long lockOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = CopyOnWriteArrayList.class;
lockOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("lock"));
} catch (Exception e) {
throw new Error(e);
}
}
}
构造函数
/**
* 创建空数组
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* 构建一个包含指定collection集合的对象,CopyOnWriteArrayList容量大小和该集合大小一致,指定集合中的元素按照迭代器的顺序排列
* collection集合类型有Map、set、List等子类,所以入参可以是多种类型
* CopyOnWriteArrayList保证数组中元素类型是Object
* @param c 指定集合
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
//c.toArray 可能不会返回正确的Object[]类型,这边可能会利用多态的性质,如 A a = new B()
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
/**
* 构建一个包含指定数组的对象
* 生成数组的新副本,并让该对象中的数组指向它
* @param toCopyIn 指定数组
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
方法说明
接下来按照类的声明顺序介绍方法,有必要的情况下结合例子进行说明。
简单方法
/**
* 获取数组
* 之所以没有加上private访问修饰符,是因为在CopyOnWriteArraySet类中使用了该方法
* 加上final防止继承类去覆写该方法
* @return 数组
*/
final Object[] getArray() {
return array;
}
/**
* 设置数组
* @param a 新数组
*/
final void setArray(Object[] a) {
array = a;
}
/**
* 获取数组元素的个数
* @return 数组元素的个数
*/
public int size() {
return getArray().length;
}
/**
* 判断数组是否为空
* @return 数组是否为空
*/
public boolean isEmpty() {
return size() == 0;
}
/**
* 判断两个对象是否相等
* @param o1 对象
* @param o2 对象
* @return 两个对象是否相等
*/
private static boolean eq(Object o1, Object o2) {
return (o1 == null) ? o2 == null : o1.equals(o2);
}
/**
* 正向遍历,获取从指定起始索引到指定结束索引之间搜索指定元素的索引
* 若指定区间不存在指定元素的话则返回-1
* @param o 对象
* @param elements 数组
* @param index 指定起始索引
* @param fence 指定结束索引
* @return 指定元素的索引
*/
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
/**
* 反向遍历,获取从指定起始索引到索引为0之间搜索指定元素的索引
* @param o 对象
* @param elements 数组
* @param index 指定起始索引
* @return 指定元素的索引
*/
private static int lastIndexOf(Object o, Object[] elements, int index) {
if (o == null) {
for (int i = index; i >= 0; i--)
if (elements[i] == null)
return i;
} else {
for (int i = index; i >= 0; i--)
if (o.equals(elements[i]))
return i;
}
return -1;
}
/**
* 判断数组中是否包含指定元素
* @param o 指定元素
* @return 数组是否包含指定元素
*/
public boolean contains(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length) >= 0;
}
/**
* 正向遍历,获取指定元素的索引
* 若未发现指定元素则返回-1
* @param o 指定元素
* @return 指定元素的索引
*/
public int indexOf(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length);
}
/**
* 正向遍历,获取从指定起始索引处开始搜索指定元素的索引
* 若未发现指定元素则返回-1
* @param e 指定元素
* @param index 指定起始索引
* @return 指定元素的索引
*/
public int indexOf(E e, int index) {
Object[] elements = getArray();
return indexOf(e, elements, index, elements.length);
}
/**
* 反向遍历,获取指定元素的索引
* @param o 指定元素
* @return 指定元素的索引
*/
public int lastIndexOf(Object o) {
Object[] elements = getArray();
return lastIndexOf(o, elements, elements.length - 1);
}
/**
* 反向遍历,获取从指定起始索引处开始搜索指定元素的索引
* @param e 指定元素
* @param index 指定起始索引
* @return 指定元素的索引
*/
public int lastIndexOf(E e, int index) {
Object[] elements = getArray();
return lastIndexOf(e, elements, index);
}
/**
* 拷贝新的CopyOnWriteArrayList对象,没有拷贝对象中的数组,属于浅拷贝
* @return 对象
*/
public Object clone() {
try {
@SuppressWarnings("unchecked")
CopyOnWriteArrayList<E> clone =
(CopyOnWriteArrayList<E>) super.clone();
clone.resetLock();
return clone;
} catch (CloneNotSupportedException e) {
throw new InternalError();
}
}
/**
* 返回一个包含所有列表元素的有序(按照添加顺序)数组
* 此方法是创建一个新数组,方便使用者能够随便操作新数组
* @return 新数组
*/
public Object[] toArray() {
Object[] elements = getArray();
return Arrays.copyOf(elements, elements.length);
}
/**
* 将列表的所有元素放入到指定数组中并返回
*
* 注意:T类型要么是数组中数据的相同类型,要么是数组中数据的父类型,运用多态性质
* 若传入的新数组容量 < 列表容量,则取它的类类型来创建一个包含列表元素的新数组,并返回
* 若传入的新数组容量 > 列表容量,则将列表中的元素按照顺序拷贝到新数组中,同时将新数组中索引为size的值设置成null
*
* 一开始我也好奇为啥要在索引为size上设置个null呢?
* 看了注释加上自我的理解,若传入的新数组是个空数组的话,那么除了拷贝列表元素后剩余的所有空间的值都为null,此时在给索引为size的值设置成null似乎没有多大
* 意思;另外一种情况是若传入的新数组不是个空数组,那这个设置就有意义了,传入的新数组的某些元素会被列表元素覆盖,同时有个null,剩下的才是自己本身的数据,呈现这样子一种效果
*
* List<Integer> list = new ArrayList<>();
* list.add(11);
*
* Integer[] str = new Integer[]{1,2,3,4,5,6,7,8,9,10};
* Integer[] s1 = list.toArray(str);
*
* for (Integer s : s1) {
* System.out.println(s + ",");
* }
*
* 输出结果:11,null,3,4,5,6,7,8,9,10,
* 那么设置这个null的意义就在于能够确定列表中元素个数(长度),但有个前提就是调用者知道链表中的所有节点信息不存在null才有意义,目前我只有想到这一种情况下有用!
*
* @param a 指定数组
* @return 填充完列表元素的指定数组
*/
public <T> T[] toArray(T a[]) {
Object[] elements = getArray();
int len = elements.length;
if (a.length < len)
return (T[]) Arrays.copyOf(elements, len, a.getClass());
else {
System.arraycopy(elements, 0, a, 0, len);
if (a.length > len)
a[len] = null;
return a;
}
}
/**
* 判断数组中是否包含指定集合中的所有元素
* 集合中的元素但凡在数组中未包含则返回false
* @param c 指定集合
* @return 数组中是否包含指定集合中的所有元素
*/
public boolean containsAll(Collection<?> c) {
Object[] elements = getArray();
int len = elements.length;
for (Object e : c) {
if (indexOf(e, elements, 0, len) < 0)
return false;
}
return true;
}
/**
* 集合与数组取交集
* 最终数组中只包含与集合共有的元素,相当于在修改数组
* @param c 指定集合
* @return 数组元素是否被修改成功
*/
public boolean retainAll(Collection<?> c) {
if (c == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
// temp array holds those elements we know we want to keep
int newlen = 0;
Object[] temp = new Object[len];
for (int i = 0; i < len; ++i) {
Object element = elements[i];
if (c.contains(element))
temp[newlen++] = element;
}
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
/**
* 清空数组中的元素
*/
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
setArray(new Object[0]);
} finally {
lock.unlock();
}
}
/**
* 遍历数组,并对数组中的元素进行指定处理
* 读取时不会发生冲突,因为添加、删除、替换等操作都是使用新副本,只不过会出现实时数据不一致,但最终是一致的
* @param action 函数式接口,对数组中的元素指定处理
*/
public void forEach(Consumer<? super E> action) {
if (action == null) throw new NullPointerException();
Object[] elements = getArray();
int len = elements.length;
for (int i = 0; i < len; ++i) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
}
/**
* 根据指定条件移除元素
* @param filter 使用指定条件来过滤元素
* @return 是否移除成功
*/
public boolean removeIf(Predicate<? super E> filter) {
if (filter == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
int newlen = 0;
Object[] temp = new Object[len];
for (int i = 0; i < len; ++i) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
if (!filter.test(e))
temp[newlen++] = e;
}
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
/**
* 根据指定规则替换所有旧元素
* operator.apply方法:旧元素作为入参传入,根据规则返回新元素,然后进行替换
* @param operator 指定规则,函数式接口
*/
public void replaceAll(UnaryOperator<E> operator) {
if (operator == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
for (int i = 0; i < len; ++i) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
newElements[i] = operator.apply(e);
}
setArray(newElements);
} finally {
lock.unlock();
}
}
/**
* 根据指定规则对数组中的元素进行排序
* 若没有指定规则则使用默认的升序进行排序
* 指定规则后会调用自定义比较器中的compare方法进行比较排序
* @param c 自定义比较器,覆写compare方法
*/
public void sort(Comparator<? super E> c) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
Object[] newElements = Arrays.copyOf(elements, elements.length);
@SuppressWarnings("unchecked") E[] es = (E[])newElements;
Arrays.sort(es, c);
setArray(newElements);
} finally {
lock.unlock();
}
}
/**
* 自定义序列化
* 写入数组的长度及数组的元素方便构建
* @param s 输出流
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
s.defaultWriteObject();
Object[] elements = getArray();
// Write out array length
s.writeInt(elements.length);
// Write out all elements in the proper order.
for (Object element : elements)
s.writeObject(element);
}
/**
* 自定义反序列化
* @param s 输入流
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
//生成新的锁对象
resetLock();
int len = s.readInt();
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, len);
Object[] elements = new Object[len];
for (int i = 0; i < len; i++)
elements[i] = s.readObject();
setArray(elements);
}
/**
* 获取数组元素的字符串
* @return 数组元素的字符串
*/
public String toString() {
return Arrays.toString(getArray());
}
/**
* 先判断当前对象与指定对象是否指向同一个对象,就是在判断地址
* 紧接着判断指定对象属于List的子类
* 紧接着获取该对象的迭代器
* 若两个迭代器的元素个数不相等,则返回false
* 若两个迭代器的元素个数相等,则将两个迭代器的元素进行对应的比较,但凡出现对应的元素不相等则返回false
* @param o 指定对象
* @return 当前对象与指定对象是否相等
*/
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof List))
return false;
List<?> list = (List<?>)(o);
Iterator<?> it = list.iterator();
Object[] elements = getArray();
int len = elements.length;
for (int i = 0; i < len; ++i)
if (!it.hasNext() || !eq(elements[i], it.next()))
return false;
if (it.hasNext())
return false;
return true;
}
/**
* 获取哈希值
* @return 哈希值
*/
public int hashCode() {
int hashCode = 1;
Object[] elements = getArray();
int len = elements.length;
for (int i = 0; i < len; ++i) {
Object obj = elements[i];
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
return hashCode;
}
/**
* 获取分割迭代器
* 由于该方法涉及到另外一个接口,会另外新起一篇文章来讲解该内容,这里就不做阐述
* 附上文章地址:http://zlia.tech/2019/08/28/explain-arraylist-spliterator-sourcecode
* @return
*/
public Spliterator<E> spliterator() {
return Spliterators.spliterator
(getArray(), Spliterator.IMMUTABLE | Spliterator.ORDERED);
}
/**
* 获取指定起始索引到指定结束索引之间的元素,简称获取指定子集
* 指定区间中的元素包括起始索引,不包括结束索引
* 若起始索引与结束索引相等,则返回空元素
* 对子集的操作,即调用set、add、remove等方法将会影响到整个数组
* 但在先获取子集后,又对整个数组的结构进行修改,这时在遍历子集则会导致报错,而对于整体的非结构性修改则不会报错,不过依然会影响到子集
* 所以在获取子集后最好不要修改数组的结构
*
* 代码片段与ArrayList是类似的,可参考ArrayList文章:http://zlia.tech/2019/08/16/explain-arraylist-sourcecode
* @param fromIndex 起始索引
* @param toIndex 结束索引
* @return 指定区间中的所有元素,称为子集
*/
public List<E> subList(int fromIndex, int toIndex) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (fromIndex < 0 || toIndex > len || fromIndex > toIndex)
throw new IndexOutOfBoundsException();
return new COWSubList<E>(this, fromIndex, toIndex);
} finally {
lock.unlock();
}
}
/**
* 重置锁,生成新的锁对象
*/
private void resetLock() {
UNSAFE.putObjectVolatile(this, lockOffset, new ReentrantLock());
}
获取元素
/**
* 获取指定索引的元素
* @param a 数组
* @param index 指定索引
* @return 指定索引的元素
*/
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* 获取指定索引的元素
* @param index 指定索引
* @return 指定索引的元素
*/
public E get(int index) {
return get(getArray(), index);
}
修改元素
/**
* 将指定索引处的元素修改成指定元素
* 在执行操作之前,先加上锁,接着生成数组的新副本,在新副本中替换元素,最后将数组指向新副本并释放锁
* 在未释放锁之前,其他线程无法进入,这样子就保证了线程安全
* 每次调用该方法都会造成新副本数组的生成,导致内存飙升
* @param index 指定索引
* @param element 新元素
* @return 旧元素
*/
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
添加元素
/**
* 添加指定元素到列表尾部
* 在执行操作之前,先加上锁,接着生成数组的新副本,并扩充其容量,最后将数组指向新副本并释放锁
* @param e 指定元素
* @return 是否添加成功
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
/**
* 添加指定元素到指定索引处
* 由于每次都会生成新副本,原先数组的前index元素列表会拷贝到新副本中,再者原先数据的后index元素列表会拷贝到新副本中
* 原先数组中的所有元素都拷贝到了新副本中,最终在新副本中的index位置为null,最后在设置指定元素即可
* @param index 指定索引
* @param element 指定元素
*/
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
/**
* 若数组中未包含指定元素则进行添加到尾部
* 若数组中已经存在指定元素则返回false
* @param e 指定元素
* @return 是否添加成功
*/
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
/**
* 若新副本中未包含指定元素则进行添加到尾部
* 若是新副本中已经存在指定元素则返回false
* 至始至终snapshot都是用来做与新副本进行比较的
* @param e 指定元素
* @param snapshot 数组,有可能成为旧数组
* @return 是否添加成功
*/
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
//生成了新的数组副本
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
/**
* 添加集合中未被数组包含的元素到数组尾部,相当于批量添加不存在的元素到尾部
* 集合中重复的元素只会被添加一次
* @param c 指定集合
* @return 添加到数组中的元素个数
*/
public int addAllAbsent(Collection<? extends E> c) {
Object[] cs = c.toArray();
if (cs.length == 0)
return 0;
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
int added = 0;
// uniquify and compact elements in cs
for (int i = 0; i < cs.length; ++i) {
Object e = cs[i];
if (indexOf(e, elements, 0, len) < 0 &&
indexOf(e, cs, 0, added) < 0)
cs[added++] = e;
}
if (added > 0) {
Object[] newElements = Arrays.copyOf(elements, len + added);
System.arraycopy(cs, 0, newElements, len, added);
setArray(newElements);
}
return added;
} finally {
lock.unlock();
}
}
/**
* 将指定集合添加到数组尾部
* @param c 指定集合
* @return 是否添加成功
*/
public boolean addAll(Collection<? extends E> c) {
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
if (cs.length == 0)
return false;
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len == 0 && cs.getClass() == Object[].class)
setArray(cs);
else {
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
System.arraycopy(cs, 0, newElements, len, cs.length);
setArray(newElements);
}
return true;
} finally {
lock.unlock();
}
}
/**
* 将指定集合添加到数组的指定索引处
* @param index 指定索引
* @param c 指定集合
* @return 是否添加成功
*/
public boolean addAll(int index, Collection<? extends E> c) {
Object[] cs = c.toArray();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
if (cs.length == 0)
return false;
int numMoved = len - index;
Object[] newElements;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + cs.length);
else {
newElements = new Object[len + cs.length];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index,
newElements, index + cs.length,
numMoved);
}
System.arraycopy(cs, 0, newElements, index, cs.length);
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
移除元素
/**
* 移除数组中指定索引处的元素
* @param index 指定索引
* @return 旧元素
*/
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
/**
* 移除数组中的指定元素
* @param o 指定元素
* @return 是否移除成功
*/
public boolean remove(Object o) {
Object[] snapshot = getArray();
int index = indexOf(o, snapshot, 0, snapshot.length);
return (index < 0) ? false : remove(o, snapshot, index);
}
/**
* 移除数组中的指定索引处的元素
* @param o 指定元素
* @param snapshot 数组,有可能是旧数组
* @param index 指定索引
* @return 是否移除成功
*/
private boolean remove(Object o, Object[] snapshot, int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) findIndex: {
//这部分代码是获取新副本中指定元素的索引,也就是获取最新的索引,看看新副本中有没有存在等于指定元素的更小索引
int prefix = Math.min(index, len);
for (int i = 0; i < prefix; i++) {
if (current[i] != snapshot[i] && eq(o, current[i])) {
index = i;
break findIndex; //表示跳出指定代码块
}
}
//若在新副本中未找到指定元素的索引则index不会被改变,此时的情况应该是:旧数组的长度大于新副本数组的长度,那么最终会返回false
if (index >= len)
return false;
//判断新副本的指定索引处的元素是否与指定元素相等,若相等则说明该位置的元素并未发生改变
if (current[index] == o)
break findIndex;
//此时的情况是:新副本数组的长度大于旧数组的长度,获取新副本中指定元素的索引
index = indexOf(o, current, index, len);
if (index < 0)
return false;
}
Object[] newElements = new Object[len - 1];
System.arraycopy(current, 0, newElements, 0, index);
System.arraycopy(current, index + 1,
newElements, index,
len - index - 1);
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
/**
* 移除指定起始索引到结束索引之间的所有元素
* @param fromIndex 指定起始索引
* @param toIndex 指定结束索引
*/
void removeRange(int fromIndex, int toIndex) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (fromIndex < 0 || toIndex > len || toIndex < fromIndex)
throw new IndexOutOfBoundsException();
int newlen = len - (toIndex - fromIndex);
int numMoved = len - toIndex;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, newlen));
else {
Object[] newElements = new Object[newlen];
System.arraycopy(elements, 0, newElements, 0, fromIndex);
System.arraycopy(elements, toIndex, newElements,
fromIndex, numMoved);
setArray(newElements);
}
} finally {
lock.unlock();
}
}
/**
* 数组中移除指定集合的所有元素
* @param c 指定集合
* @return 是否移除成功
*/
public boolean removeAll(Collection<?> c) {
if (c == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
// temp array holds those elements we know we want to keep
int newlen = 0;
Object[] temp = new Object[len];
for (int i = 0; i < len; ++i) {
Object element = elements[i];
if (!c.contains(element))
temp[newlen++] = element;
}
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
迭代器
/**
* 获取迭代器
* @return 迭代器
*/
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
/**
* 获取迭代器
* @return 迭代器
*/
public ListIterator<E> listIterator() {
return new COWIterator<E>(getArray(), 0);
}
/**
* 获取从指定起始索引开始的列表迭代器
* 列表迭代器中的元素是从指定索引开始到结束索引
* @param index 指定起始索引
* @return 列表迭代器
*/
public ListIterator<E> listIterator(int index) {
Object[] elements = getArray();
int len = elements.length;
if (index < 0 || index > len)
throw new IndexOutOfBoundsException("Index: "+index);
return new COWIterator<E>(elements, index);
}
/**
* 列表迭代器,正向迭代
* 可获取上一个元素、下一个元素及索引
*/
static final class COWIterator<E> implements ListIterator<E> {
//当迭代器被创建后,只拿到当前数组的引用,也就是说只拥有当前数组的元素
//而随着后面列表的add、remove、repalce都是在操作新副本,这些变动并不会反映到当前的引用,相当于是两个引用
private final Object[] snapshot;
//下一个元素的索引
private int cursor;
/**
* 初始化参数
* @param elements 当前数组
* @param initialCursor 下一个元素的索引
*/
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
/**
* 判断是否有下一个元素
* @return 是否有下一个元素
*/
public boolean hasNext() {
return cursor < snapshot.length;
}
/**
* 判断是否有前一个元素
* @return 是否有前一个元素
*/
public boolean hasPrevious() {
return cursor > 0;
}
/**
* 获取下一个元素的值
* 若不存在则抛出异常
* @return 下一个元素的值
*/
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
/**
* 获取上一个元素
* 若不存在则抛出异常
* @return 上一个元素
*/
@SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
}
/**
* 获取下一个元素的索引
* @return 下一个元素的索引
*/
public int nextIndex() {
return cursor;
}
/**
* 获取上一个元素的索引
* @return 上一个元素的索引
*/
public int previousIndex() {
return cursor-1;
}
/**
* 不支持,为什么不支持在数据结构那一栏中有提到
*/
public void remove() {
throw new UnsupportedOperationException();
}
/**
* 不支持,为什么不支持在数据结构那一栏中有提到
*/
public void set(E e) {
throw new UnsupportedOperationException();
}
/**
* 不支持,为什么不支持在数据结构那一栏中有提到
*/
public void add(E e) {
throw new UnsupportedOperationException();
}
/**
* 遍历元素,只能遍历一次
* 与forEach的区别在于:可以遍历多次
* @param consumer 函数式接口,声明如何处理元素的函数
*/
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
Object[] elements = snapshot;
final int size = elements.length;
for (int i = cursor; i < size; i++) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
cursor = size;
}
}
总结
-
CopyOnWriteArrayList允许存放Null。
-
底层通过生成数组的新副本实现,故内存占用是个明显的问题。
-
多线程情况下,可能读取旧数据,只能保证数据的最终一致性。
-
CopyOnWriteArrayList适用于读多写少的场景。
-
CopyOnWriteArrayList在性能上没有ArrayList、LinkedList好,毕竟加了锁!
-
CopyOnWriteArrayList没有扩容机制,每次添加节点前就拷贝源数组到新数组中,而新数组与源数组的长度差为1。
重点关注
线程安全 底层是通过生成数组的新副本实现 由于每次都生成新副本,故内存占用会相对更大 多线程情况下,可能读取到旧数据(读取在添加之前执行)
浙公网安备 33010602011771号