文章目录
前言
什么是线程安全?
线程安全是指在多线程环境下,对共享资源(如变量、对象、数据结构等)的访问操作能够正确地进行,不会导致不确定的结果或破坏数据的一致性。
在之前集合的学习中,ArrayList、LinkedList、HashMap等都是线程不安全的集合,在单线程环境下,程序的执行是按照顺序逐行执行的,因此不会出现多个线程同时访问和修改共享资源的情况。然而,在多线程环境下,多个线程可能同时访问和修改共享资源,这就引入了并发问题。
Copy-On-Write简称COW,是一种用于集合的并发访问的优化策略。基本思想是:当我们往一个集合容器中写入元素时(添加、修改、删除),并不会直接在集合容器中写入,而是先将当前集合容器进行Copy,复制出一个新的容器,然后新的容器里写入元素,写入操作完成之后,再将原容器的引用指向新的容器。
而CopyOnWriteArrayList就是一种线程安全的List集合。
一、CopyOnWriteArrayList的优缺点
- 线程安全:CopyOnWriteArrayList是线程安全的,可以在多线程环境中使用而无需额外的同步措施。多个线程可以同时对CopyOnWriteArrayList进行读操作,而不会导致数据不一致或抛出并发修改异常。
- 写时复制:当进行修改操作(如添加、修改、删除)时,CopyOnWriteArrayList会创建一个新的副本,并在副本上进行修改操作,而原始数据保持不变。这种写时复制的机制保证了读操作的高效性,因为读操作不需要加锁。
- 内存占用:由于每次写操作都会复制整个数据集合,因此CopyOnWriteArrayList的内存占用较高。在处理大数据集合时,需要注意内存的消耗,并根据实际情况选择合适的数据结构。
- 实时性:由于写操作不会阻塞读操作,因此读操作可能会读取到旧的数据。在需要实时性较高的场景下,CopyOnWriteArrayList可能不适合,可以考虑其他线程同步机制。
二、CopyOnWriteArrayList的适用场景
-
读多写少的场景:由于CopyOnWriteArrayList的写操作会复制整个数组,因此适用于读多写少的场景。在这种场景下,读操作可以高效地并发执行,而写操作的开销相对较高,但由于写操作较少,整体性能仍然可接受。
-
数据不频繁变动的场景:CopyOnWriteArrayList适合存储不经常变动的数据集合。如果数据集合的修改频率很高,每次修改都会触发全新的复制,这可能导致内存占用较大,影响性能。
三、CopyOnWriteArrayList核心源码阅读
1.实现接口及成员变量
CopyOnWriteArrayList类实现了List接口,表示一个List集合作为底层实现的,RandomAccess是一个标记接口,用于标识实现该接口的类支持随机访问;
Cloneable是一个标记接口,用于标识实现该接口的类可以进行克隆(复制)操作;
Serializable是一个标记接口,用于标识实现该接口的类可以进行序列化操作。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//创建ReentrantLock对象lock
final transient ReentrantLock lock = new ReentrantLock();
//声明底层数据结构是Object类型的数组
private transient volatile Object[] array;
2.无参构造方法
默认初始化一个空的Object类型的数组
//调用了setArray()方法并传入了一个空的Object数组
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
//将参数中的数组赋值给成员变量array
final void setArray(Object[] a) {
array = a;
}
3.add方法
- add(E e)方法:添加一个元素
//获取当前 CopyOnWriteArrayList 实例的底层数组
final Object[] getArray() {
return array;
}
public boolean add(E e) {
//通过ReentrantLock类定义锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取当前对象的内部数组
Object[] elements = getArray();
//得到数组长度
int len = elements.length;
//对原数组进行复制操作,复制的结果保存在一个新数组中
Object[] newElements = Arrays.copyOf(elements, len + 1);
//将添加的值e添加到新数组中
newElements[len] = e;
//将新数组赋值给底层数组
setArray(newElements);
//添加元素成功,返回true
return true;
} finally {
//释放锁
lock.unlock();
}
}
- add(int index, E element)方法:添加元素到指定位置
public void add(int index, E element) {
//通过ReentrantLock类定义锁
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;
//如果需要移动的元素数量等于0,说明是在末尾添加元素
if (numMoved == 0)
//将原数组和新长度复制给新数组
newElements = Arrays.copyOf(elements, len + 1);
else {
//新数组长度为原数组长度+1
newElements = new Object[len + 1];
//复制旧数组[0,index]的数据到新数组
System.arraycopy(elements, 0, newElements, 0, index);
//复制旧数组从index开始复制numMoved个到新数组index+1开始复制numMoved;
//其作用刚好空出了指定位置index来存放要加入的值
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//向新数组的指定位置赋值;
newElements[index] = element;
//将新数组赋值给底层数组
setArray(newElements);
} finally {
lock.unlock();//释放锁
}
}
- addAll(Collection<? extends E> c):将指定集合中的所有元素添加到列表中
public boolean addAll(Collection<? extends E> c) {
//如果集合对象c的类信息等于CopyOnWriteArrayList的类信息,就对集合做强制
//类型转换,然后调用其 getArray() 方法来获取底层数组,否则,调用 toArray() 方法将集合转换为数组
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
//如果数组cs的长度为0,表示没有要添加的元素,直接返回false
if (cs.length == 0)
return false;
//定义锁
final ReentrantLock lock = this.lock;
lock.lock();//加锁
try {
//获取当前对象的内部数组
Object[] elements = getArray();
//得到数组长度
int len = elements.length;
//如果len长度为0并且cs的类型是Object[],
if (len == 0 && cs.getClass() == Object[].class)
将cs数组赋值给底层数组
setArray(cs);
else {
//创建新数组newElements,长度为len + cs.length,并将elements数组的内容复制到新数组中
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
//将cs数组的内容复制到newElements数组的末尾。
System.arraycopy(cs, 0, newElements, len, cs.length);
//将新数组赋值给底层数组
setArray(newElements);
}
return true;
} finally {
lock.unlock();//释放锁
}
}
4.set方法
- set(int index, E element):修改指定位置元素
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//获取当前对象的内部数组
Object[] elements = getArray();
//获取索引index位置的元素
E oldValue = get(elements, 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();//释放锁
}
}
5.remove方法
- remove(int index):按索引删除指定元素
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;
//如果要移动的元素数量为0,则表示删除的是最后一个元素
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//创建新数组newElements,长度为len-1
Object[] newElements = new Object[len - 1];
//将旧数组索引index之前的元素复制到newElements数组中
System.arraycopy(elements, 0, newElements, 0, index);
//再将就数组索引index+1之后的元素复制到newElements数组中
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
//将新数组赋值给底层数组
setArray(newElements);
}
return oldValue;//返回删除的元素
} finally {
lock.unlock();
}
}
- removeRange(int fromIndex, int 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;
//如果要移动的元素数量为0,则表示删除的是数组的末尾
if (numMoved == 0)
setArray(Arrays.copyOf(elements, newlen));
else {
//创建新数组newElements,长度为移除指定范围后的新长度
Object[] newElements = new Object[newlen];
//将旧数组索引fromIndex之前的元素复制到newElements数组中
System.arraycopy(elements, 0, newElements, 0, fromIndex);
//将旧数组索引toIndex之后的元素复制到newElements数组中
System.arraycopy(elements, toIndex, newElements,
fromIndex, numMoved);
//将新数组赋值给底层数组
setArray(newElements);
}
} finally {
lock.unlock();
}
}
- removeAll(Collection<?> c):移除指定 collection 中的所有元素
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;
//数组长度不为0
if (len != 0) {
// temp array holds those elements we know we want to keep
int newlen = 0;
//创建新数组temp ,长度为len;
Object[] temp = new Object[len];
//通过循环做过滤操作,如果该元素不在要删除的集合中,则添加至数组temp中
for (int i = 0; i < len; ++i) {
Object element = elements[i];
if (!c.contains(element))
temp[newlen++] = element;
}
//如果不相等,表示有元素需要被移除
if (newlen != len) {
//将temp数组中前newlen个元素复制到新数组中,并将该数组赋值给底层数组
setArray(Arrays.copyOf(temp, newlen));
return true;//删除成功,返回true
}
}
//如果长度为0,直接返回false表示没有进行移除操作
return false;
} finally {
lock.unlock();
}
}
6.get方法
- get(int index):根据指定下标获取元素
public E get(int index) {
//get(Object[] a, int index)方法
//getArray()获取当前对象的内部数组
return get(getArray(), index);
}
- get(Object[] a, int index):根据指定下标在指定数组中获取元素
@SuppressWarnings("unchecked")
//通过下标在数组中获取元素
private E get(Object[] a, int index) {
return (E) a[index];
}
7.sort方法
- sort(Comparator<? super E> c):排序方法;需要传入Comparator接口的实现类,确定比较规则
public void sort(Comparator<? super E> c) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//获取当前对象的内部数组
Object[] elements = getArray();
//将elements数组的内容复制到newElements数组中
Object[] newElements = Arrays.copyOf(elements, elements.length);
//将newElements数组转换为泛型数组E[] es
@SuppressWarnings("unchecked") E[] es = (E[])newElements;
//根据传入的实现类,对数组进行排序
Arrays.sort(es, c);
//将新数组赋值给底层数组
setArray(newElements);
} finally {
lock.unlock();
}
}
8.clear方法
- clear():清空集合中的所有元素
public void clear() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//将一个长度为0的新数组赋值给底层数组
setArray(new Object[0]);
} finally {
lock.unlock();
}
}
9.equals方法和hashCode方法
- equals(Object o):判断当前对象是否与o相等
public boolean equals(Object o) {
//引用数据类型==比较内存地址,内存地址相同,直接返回true
if (o == this)
return true;
//instanceof判断对象o是否是List的实例,不是返回false
if (!(o instanceof List))
return false;
//将给定对象o转换为List类型
List<?> list = (List<?>)(o);
//获取List对象的迭代器it
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()))
//在!it.hasNext()循环未结束,迭代器it没有下一个元素了,元素长度不同,返回false
//!eq(elements[i], it.next():判断当前元素与迭代器返回的元素,不同,则返回false
return false;
//循环结束,迭代器it还有下一个元素,元素长度不同,返回false
if (it.hasNext())
return false;
return true;
}
- hashCode():计算当前对象的哈希码值
public int hashCode() {
int hashCode = 1;
//获取当前对象的内部数组
Object[] elements = getArray();
//得到数组长度
int len = elements.length;
for (int i = 0; i < len; ++i) {
//将数组中的每个依次赋值给obj变量。
Object obj = elements[i];
//如果obj不为null,用31*hashCode+obj.hashCode()进行计算;
//如果obj为null,用31*hashCode+0进行计算;
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
//循环结束后,将计算好的hashCode返回
return hashCode;
}
总结
CopyOnWriteArrayList为开发者提供了一种简单而有效的线程安全集合,可以在并发环境中安全地访问和修改数据,通过写时复制的机制保证了读操作的高效性。它适用于读多写少、数据不频繁变动的场景。