继承体系
CopyOnWriteArrayList存在的目的是为了解决在高并发下list的读写。设计上希望只阻塞写行为,不会阻塞读行为。CopyOnWriteArrayList设计就基于此,在内部含有ReentrantLock用作修改时加锁,CopyOnWriteArrayList下有很多可以写方法,但是只有一把锁,意味着同一个时间只会有一个写操作发生。在锁锁住期间,进行写操作期间,会将此数据整个拷贝一份进行修改,完毕瞬间才会进行更新数据,这样就可以保证读操作不会被阻塞。这么说来,这个工具类只保证最终一致性。
重要变量
// 重入锁,读写并发的支持
final transient ReentrantLock lock = new ReentrantLock();
// 存储的真实的地方,只允许getArray/setArray方法对此访问
private transient volatile Object[] array;
// 用来cas操作的unsafe类
private static final sun.misc.Unsafe UNSAFE;
// lock重入锁的偏移量
private static final long lockOffset;
构造方法
public CopyOnWriteArrayList() {
//默认调用setArray赋值空数组
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
//如果当前传入的是CopyOnWriteArrayList集合,则直接得到这个类的Array赋值给当前类
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
// 否则得到数组,确保转换之后得到Object数组
elements = c.toArray();
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
// 对当前数组数据进行赋值
setArray(elements);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
//将传入数组进行转换,变为对象数组
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
final void setArray(Object[] a) {
array = a;
}
可以看到,构造函数实现还是很简单的,最终都是在调用setArray进行数据的赋值。
重要方法
新增元素
新增元素相关方法如下:
- add(E e) 添加一个元素到末尾。
- add(int index, E element) 在某一个位置添加一个元素
- addIfAbsent(E e)方法,判断e是否存在在数据内,如果不存在则放入尾部否则不做处理
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) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获取旧数组
Object[] elements = getArray();
int len = elements.length;
// 检查是否越界, 可以等于len
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
// 如果插入的位置是最后一位
// 那么拷贝一个n+1的数组, 其前n个元素与旧数组一致
newElements = Arrays.copyOf(elements, len + 1);
else {
// 如果插入的位置不是最后一位
// 那么新建一个n+1的数组
newElements = new Object[len + 1];
// 拷贝旧数组前index的元素到新数组中
System.arraycopy(elements, 0, newElements, 0, index);
// 将index及其之后的元素往后挪一位拷贝到新数组中
// 这样正好index位置是空出来的
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
// 将元素放置在index处
newElements[index] = element;
setArray(newElements);
} finally {
// 释放锁
lock.unlock();
}
}
public boolean addIfAbsent(E e) {
// 获取元素数组, 取名为快照
Object[] snapshot = getArray();
// 检查如果元素不存在,直接返回false
// 如果存在再调用addIfAbsent()方法添加元素
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
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++)
// 假定当前快照相似,在common范围内不相等的数据位置数据和目标进行比对
// 如果相等,意味着已经存在在数据里面,返回false
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
// 拷贝一份n+1的数组
Object[] newElements = Arrays.copyOf(current, len + 1);
// 将元素放在最后一位
newElements[len] = e;
setArray(newElements);
return true;
} finally {
// 释放锁
lock.unlock();
}
}
删除元素
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)
// 如果移除的是最后一位
// 那么直接拷贝一份n-1的新数组, 最后一位就自动删除了
setArray(Arrays.copyOf(elements, len - 1));
else {
// 如果移除的不是最后一位
// 那么新建一个n-1的新数组
Object[] newElements = new Object[len - 1];
// 将前index的元素拷贝到新数组中
System.arraycopy(elements, 0, newElements, 0, index);
// 将index后面(不包含)的元素往前挪一位
// 这样正好把index位置覆盖掉了, 相当于删除了
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
// 释放锁
lock.unlock();
}
}
public boolean remove(Object o) {
Object[] snapshot = getArray();
// 从0-length范围内寻找o对象
int index = indexOf(o, snapshot, 0, snapshot.length);
// 从index之后的范围内删除o对象
return (index < 0) ? false : remove(o, snapshot, index);
}
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;
}
}
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();
}
}
变更元素
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获得数据
Object[] elements = getArray();
// 获得index位置的数据
E oldValue = get(elements, index);
// 拷贝一份数据出来,将index位置的数据变更为目标数据
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
// 替换旧数据
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
查询元素
查询元素的方法有以下几个:
- get(intindex) 根据索引位置获得元素
- indexOf(E e,int index) 根据元素,范围,查找元素所在的索引位置
- indexOf(Object o) 根据元素,查找元素所在的索引范围
- lastIndexOf(Object o) 根据元素,从尾部开始查找元素所在的索引范围
- lastIndexOf(E e,int index) 根据元素,范围,从尾部开始查找元素所在的索引位置
public E get(int index) {
// 获取元素不需要加锁
// 直接返回index位置的元素
// 这里是没有做越界检查的, 因为数组本身会做越界检查
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
public int indexOf(E e, int index) {
Object[] elements = getArray();
return indexOf(e, elements, index, elements.length);
}
public int indexOf(Object o) {
Object[] elements = getArray();
return indexOf(o, elements, 0, elements.length);
}
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;
}
public int lastIndexOf(Object o) {
Object[] elements = getArray();
return lastIndexOf(o, elements, elements.length - 1);
}
public int lastIndexOf(E e, int index) {
Object[] elements = getArray();
return lastIndexOf(e, elements, index);
}
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;
}
CopyOnWriteArraySet
CopyOnWriteArraySet是线程安全的集合操作,继承自AbstractSet:
其底层使用CopyOnWriteList进行存储,所有操作都转交给了CopyOnWriteList。源码相对简单,在这里不过多描述。
总结
CopyOnWriteList是ArrayList的线程安全版本,其设计上将读写进行分离,只会对写操作进行阻塞,其内部使用ReentrantLock确保线程安全。CopyOnWriteList遇到写操作的时候都会将数据重新复制一份在此基础上进行修改最终将新拷贝的数据覆盖原来的数据。因此如果您的场景是读多写少,且数据不会很大的时候,CopyOnWriteList会很方便。