CopyOnWriteArrayList阅读笔记
一、简介
CopyOnWriteArrayList是ArrayList的线程安全版本,内部也是通过数组实现,
每次对数组的修改都完全拷贝一份新的数组来修改,修改完了再替换掉老数组,这样保证了
只阻塞写操作,不阻塞读操作,实现读写分离。
二、继承关系图
三、存储结构
- 底层采用List数组,锁采用ReentrantLock对写进行加锁,且每次修改都是直接copy一个新数组
四、源码分析
内部类
COWIterator、COWSubList、COWSubListIterator
属性
/** 用于修改时加锁:用transient修饰 */
final transient ReentrantLock lock = new ReentrantLock();
/** 用于存储元素的数组,只能通过getArray()方法和setArray()修改 */
private transient volatile Object[] array;
构造
/** 构造方法一:直接初始化一个长度为 0 的 Object数组 */
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
final void setArray(Object[] a) {
array = a;
}
/**构造方法二:传入集合Collection对象来初始化*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
// 如果是CopyOnWriteArrayList类型,则直接强转 赋值给临时数组elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
// 如果不是CopyOnWriteArrayList类型,则直接把集合转化为数组,再赋值给临时数组elements;
else {
elements = c.toArray();//集合转化为数组
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
//toArray()有可能不是Object数组,所以,如果不是Object数组,则进行数组copy为Object类型
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
// 把临时数组elments赋值给当前Object数组
setArray(elements);
}
/** 构造方法三:直接copy参数toCopyIn的元素给当前的Object数组 */
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
主要方法
public boolean add(E e)
- 添加元素,成功返回true,失败返回false,使用lock加锁
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获取数组元素
Object[] elements = getArray();
int len = elements.length;
// 将旧元素拷贝到新数组中
// 新数组大小是旧数组大小加 1
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 把元素加入到新数组的尾部
newElements[len] = e;
// 把新数组赋值给当前数组
setArray(newElements);
return true;
} finally {
// 释放锁
lock.unlock();
}
}
public void add(int index, E element)
- 在指定索引插入新的元素
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获取旧元素
Object[] elements = getArray();
int len = elements.length;
// 检查越界,index可以等于len
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
// 如果插入的位置是数组最后一尾的后面
// 那么直接新建一个len + 1的数组,并copy旧数组到新数组
newElements = Arrays.copyOf(elements, len + 1);
else {
// 新建一个len + 1数组
newElements = new Object[len + 1];
// copy 旧数组index前面的到新数组
System.arraycopy(elements, 0, newElements, 0, index);
// copy 旧新书index后面的到新数组
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
// 把新的元素插入到index
newElements[index] = element;
setArray(newElements);
} finally {
// 释放锁
lock.unlock();
}
}
**public E set(int index, E element) **
- 修改指定索引的元素
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;
// copy一个大小一样的新数组
Object[] newElements = Arrays.copyOf(elements, len);
// 替换掉原索引位的元素
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
// 如果没有变化就把元素set回去,这个是为了处理volatile的写语义问题,
/*
volatile修饰引用变量的问题即它只能保证引用本身的可见性,
并不能保证内部字段的可见性,如果想要保证内部字段的可见性最好使用CAS的数据结构,
这里还需要说明的的一点是volatile有时候修饰引用类型如boolean数组<可能>结果是没问题的
*/
setArray(elements);
}
// 返回旧元素
return oldValue;
} finally {
// 释放锁
lock.unlock();
}
}
public boolean addIfAbsent(E e)
- 如果元素在数组中存在,返回false,不存在则添加元素,并返回true
public boolean addIfAbsent(E e) {
// 取出旧数组
Object[] snapshot = getArray();
// 通过indexOf查询所有数组中是否有元素e,如果有则返回false,没有元素e 就触发addIfAbset函数
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
/** 获得 o 的索引位置 */
private static int indexOf(Object o, Object[] elements,
int index, int fence) {
if (o == null) {
// 直接判断元素是否有null,找到第一个null返回其索引下标
for (int i = index; i < fence; i++)
if (elements[i] == null)
// 找到元素,返回索引下标
return i;
} else {
// 元素不为null,则直接进行equals比较,找到第一个相等的元素返回其索引下标
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
// 如果以上都没找到,返回-1
return -1;
}
/** */
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) {
// Optimize for lost race to another addXXX operation
// 重新检查元素是否在刚获取的数组里
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
// 因为之前已经检查了,元素是不在快照中
// 但是重新获取的元素有不一样的
// 所以直接检查是否有不一样的元素,有的话,就再检查是否相同,相同则返回false
return false;
if (indexOf(e, current, common, len) >= 0)
// 如果后面还有元素,则继续检查,找到则返回false;
return false;
}
// 通过上面的方法,得出元素不存在新数组中,则开始元素copy 到n+1的新数组
Object[] newElements = Arrays.copyOf(current, len + 1);
// 添加元素到新数组的尾部
newElements[len] = e;
// 设置新数组为当前的集合数组
setArray(newElements);
return true;
} finally {
// 释放锁
lock.unlock();
}
}
**public E get(int index) **
- 获取指定索引下的元素,如果元素越界则抛异常,否则返回元素
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
public E remove(int index)
- 删除指定索引下的元素,并返回删除元素的值
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)
// 如果numMoved等于0,说明删除的是尾部元素,
// 则直接copy len-1的数组并赋值给当前集合
setArray(Arrays.copyOf(elements, len - 1));
else {
// 新建一个 size - 1的新数组
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();
}
}
public int size()
- 返回数组元素的长度
public int size() {
return getArray().length;
}
补充
无
五、总结
基本和ArrayList一样,只是没有扩容问题,每次添加元素都会直接创建一个新的数组,把老的赋值到新的数组中,然后再添加到新元素中,并进行赋值。