文章目录
CopyOnWriteArrayList
一、类结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gl6NVgDf-1585574481170)(C:\Users\MI\AppData\Roaming\Typora\typora-user-images\1585219561302.png)]
可以看到其自左向右实现了如下接口:
- Cloneable:可以实现克隆
- List:实现了List中的所有操作
- RnadomAccess:随机访问
- Setializable:可序列化
二、类属性
/** 这里使用可重入锁 */
final transient ReentrantLock lock = new ReentrantLock();
/**存储数据的数组 */
private transient volatile Object[] array;
三、构造函数
3.1 默认构造
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
final void setArray(Object[] a) {
array = a;
}
默认构造默认初始化一个长度为0的空数组
3.2其他构造
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
//如果c是CopyOnWriteArrayList的,则直接转为数组赋值
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
//否则转为数组
elements = c.toArray();
//如果该数组不是Object类型的,则拷贝为Object类型数组后赋值
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
**CopyOnWriteArrayList(E[] toCopyIn) **构造
将toCopyIn拷贝为Object类型的数组传入
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
四、方法
4.1 add(E x)
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();
}
}
- 先上锁
- 获取原数组进行拷贝,长度+1
- 将新元素加到最后
- 替换掉旧数组
- 释放锁
4.2 add(int index, E element)
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
//1. 上锁
lock.lock();
try {
//获取原数组
Object[] elements = getArray();
int len = elements.length;
//检索index
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
//新数组
Object[] newElements;
//要移动的位置的起点,将index往后的元素都要移动
int numMoved = len - index;
if (numMoved == 0)//如果要在第len个位置添加,则数组长度不够,拷贝原数组,长度+1
newElements = Arrays.copyOf(elements, len + 1);
else {//要在数组中间插入
//新建一个长度为len+1的数组
newElements = new Object[len + 1];
//将index前的元素拷贝到新数组
System.arraycopy(elements, 0, newElements, 0, index);
//将index及其以后的元素拷贝到新数组index+1开始的位置,这样留出第index位置插入新元素
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//插入新元素
newElements[index] = element;
setArray(newElements);
} finally {
//释放锁
lock.unlock();
}
}
流程:
- 先上锁,再检验下标index
- 如果在最后插入,则拷贝数组,长度加1
- 如果在中间插入,则拷贝新数组,将旧数组元素以index为界,分别拷贝到新数组的[0,index-1]和[index+1,…]位置,留出第index位置
- 将新元素放在新数组的index位置后覆盖旧数组
- 释放锁
addAll方法和以上两个逻辑类似,不赘述
4.3 get()
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
直接返回数组index位置元素
4.4 remove()
public E remove(int index) {
final ReentrantLock lock = this.lock;
//上锁
lock.lock();
try {
//旧数组
Object[] elements = getArray();
int len = elements.length;
//index位置元素
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];
//将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();
}
}
- 加锁
- 校验index
- 如果删除最后一个元素,则将最后一个元素之前的拷贝到新数组并覆盖旧数组
- 如果删除中间元素,则创建新数组,将index之前和之后的元素分别拷贝到新数组,这样就移除了index位置的元素,最后新数组覆盖旧数组
- 返回被删除的元素值
- 释放锁
4.4 removeAll()
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) {//原数组有元素
int newlen = 0;
//构造长度为len的临时数组
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;
}
}
//原数组为空,返回false
return false;
} finally {
lock.unlock();
}
}
- 加锁
- 如果原数组有值,则构造新的数组,遍历旧数组,将不需要删除的元素放入新数组,最后覆盖旧数组,返回true
- 如果原数组为空,则返回false
- 释放锁
4.5 addIfAbsent(E e)
如果该元素不在集合中,则添加该元素
扫描二维码关注公众号,回复:
10825555 查看本文章
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();//获取原数组
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false ://如果原数组不存在该元素,则返回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++)
if (current[i] != snapshot[i] && eq(e, current[i]))//i位置的元素已改变,且是当前元素,已存在,返回false
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();
}
}
分析完后,会发现除了读操作,修改、删除的思想基本一致。
五、总结
- 内部使用ReentrantLock重入锁实现线程安全
- 构造函数默认初始化数组长度为0
- 读操作不加锁,直接返回对应位置元素
- 写操作和读操作,都是通过先加锁,再拷贝旧数组,新数组长度为添加后的长度或删除后的长度,(数组长度永远是元素的个数),最后再替换掉旧数组,释放锁
- 该并发容器适合读多写少的场景。修改删除都需要拷贝,代价高
- 由于其读写分离的思想,因此读的时候可能会读到旧值,只能保证数据的最终一致性
- 写时复制会导致如果对象过大,会让内存同时存在两个大数组,就会触发youngGC或FullGC,导致STW,降低性能
CopyOnWriteArraySet
一、类属性
private final CopyOnWriteArrayList<E> al;
可见其底层是基于CopyOnWriteArrayList实现
二、构造函数
//默认构造直接使用 CopyOnWriteArrayList的默认构造
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
//
public CopyOnWriteArraySet(Collection<? extends E> c) {
if (c.getClass() == CopyOnWriteArraySet.class) {//如果是CopyOnWriteArraySet类型就直接将调用其参数为集合的构造
@SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
(CopyOnWriteArraySet<E>)c;
al = new CopyOnWriteArrayList<E>(cc.al);
}
else {
//如果不是,则调用addAllAbsent方法将c添加到集合中,如果已经存在(即有重复的)就不会添加
al = new CopyOnWriteArrayList<E>();
al.addAllAbsent(c);
}
}
三、add
public boolean add(E e) {
return al.addIfAbsent(e);
}
内部调用CopyOnWriteArrayList
的addIfAbsent
方法,这也就知道了,如果元素已存在,就不会添加进入,也就保证了唯一性。
而CopyOnWriteArraySet的其他方法都是使用CopyOnWriteArrayList
的方法