- CopyOnWriteArrayList 数据结构图
![](https://i-blog.csdnimg.cn/blog_migrate/1cc696bb3b08581a811de6b629fa45d3.png)
- 基于数组实现
- 读写分离的并发处理策略
- 读数据时不加锁,所有线程并发读
- 当有线程写数据时,先获取锁,然后复制一个新数组写入数据,然后切换数组引用指向新数组,然后释放锁
- CopyOnWriteArrayList 存储数据实现原理
- 获取锁
- 复制一个新数组,写入元素
- 将数组的对象引用切换到新数组
- 释放锁
// 新增元素
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();
}
}
- CopyOnWriteArrayList 取数据实现原理
- 取数据不加锁,直接获取数据
// 直接根据索引返回元素
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
- CopyOnWriteArrayList 删除数据实现原理
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();
}
}
- CopyOnWriteArrayList 优点与缺点
- 适用于读多写少的并发场景
- 因为写数据时要复制一份新的数组,占用大量内存
- 如果写操作比较多,因为经常复制新数组,所以效率会很低且产生大量垃圾
- 它保证最终的数据一致性,但无法保证数据实时性
- CopyOnWriteArrayList 与 CopyOnWriteArraySet
- CopyOnWriteArraySet 是基于 CopyOnWriteArrayList 实现的,底层调用的就是CopyOnWriteArrayList 的方法
- 添加元素时先判断元素是否存在,如果存在则不做任何操作,否则调用CopyOnWriteArrayList 的添加方法添加元素