1. 什么是CopyOnWriteArrayList?
CopyOnWriteArrayList
是java.util.concurrent包中提供的一个线程安全的ArrayList。它通过一种称为“写时复制”(Copy-On-Write)的方法来实现线程安全。简而言之,每当我们尝试修改这个列表(如添加、删除元素)时,它实际上并不直接在当前的列表上进行修改,而是先将当前列表复制一份,然后在这个副本上进行修改,修改完毕后再将原列表的引用指向新修改过的列表。这种机制确保了在读操作期间数据的不变性,非常适合读多写少的场景。
2. CopyOnWriteArrayList源码解析
2.1 类继承关系
- 实现List接口:List接口定义了对列表的基本操作。
- 实现RandomAccess接口:表示可以随机访问,数组具有随机访问的属性。
- 实现Cloneable接口:表示可克隆。
- 实现了Serializable接口:表示可序列化。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}
2.2 类属性
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 版本序列号
private static final long serialVersionUID = 8673264195747942595L;
// 可重入锁定
final transient ReentrantLock lock = new ReentrantLock();
// 对象数组,用于存放元素 CopyOnWriteArrayList 核心,使用volatile保证array的可见性和有序性。
private transient volatile Object[] array;
// Unsafe 类
private static final sun.misc.Unsafe UNSAFE;
// lock域的内存偏移量
private static final long lockOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = CopyOnWriteArrayList.class;
lockOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("lock"));
} catch (Exception e) {
throw new Error(e);
}
}
}
2.3 核心函数
// 向列表末尾添加一个元素
public boolean add(E e) {
// 获取可重入锁,之所以重新获取,是因为方法内部安全
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获取数组
Object[] elements = getArray();
// 获取数组长度
int len = elements.length;
// 核心,复制数组
Object[] newElements = Arrays.copyOf(elements, 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;
// 需要移动的个数为0
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之后的数据
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
// 设置数组
setArray(newElements);
}
// 返回旧值
return oldValue;
} finally {
lock.unlock();
}
}
// 此函数用于用指定的元素替代此列表指定位置上的元素,也是基于数组的复制来实现的。
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;
// 复制数组
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();
}
}
2.4 类构造器
// 默认构造器
public CopyOnWriteArrayList() {
// 设置数组
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class) // 类型相同
// 获取c集合的数组
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else { // 类型不相同
// 将c集合转化为数组并赋值给elements
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class) // elements类型不为Object[]类型
// 将elements数组转化为Object[]类型的数组
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
// 设置数组
setArray(elements);
}
2.5 内部类
CopyOnWriteArrayList
的内部类 COWIterator
是一个实现了 ListIterator
接口的迭代器类,专门为 CopyOnWriteArrayList
定制。它提供了一种安全的方式来遍历 CopyOnWriteArrayList
,即使在多线程环境中也不会抛出 ConcurrentModificationException
。我们来逐个分析这个内部类的主要部分。
static final class COWIterator<E> implements ListIterator<E> {
// 数组快照,所有的迭代操作都是在这个数组快照上进行的,保证了在迭代过程中,即使原列表被修改,迭代器仍然可以保持一致的视图。后续在使用示例中会表示出来。
private final Object[] snapshot;
// 这个字段是一个指针,指向下一个要返回的元素的索引
private int cursor;
// 构造器
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
// 判断是否还有下一个元素
public boolean hasNext() {
return cursor < snapshot.length;
}
// 判断是否还有上一个元素
public boolean hasPrevious() {
return cursor > 0;
}
// 用于获取下一个元素,并更新cursor的位置
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
// 用于获取下一个元素,并更新cursor的位置
@SuppressWarnings("unchecked")
public E previous() {
if (! hasPrevious())
throw new NoSuchElementException();
return (E) snapshot[--cursor];
}
// 返回下一个元素索引
public int nextIndex() {
return cursor;
}
// 返回上一个元素索引
public int previousIndex() {
return cursor-1;
}
// 不支持操作
public void remove() {
throw new UnsupportedOperationException();
}
// 不支持操作
public void set(E e) {
throw new UnsupportedOperationException();
}
// 不支持操作
public void add(E e) {
throw new UnsupportedOperationException();
}
// 允许使用 lambda 表达式或方法引用来迭代剩余的元素
@Override
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
Object[] elements = snapshot;
final int size = elements.length;
for (int i = cursor; i < size; i++) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
cursor = size;
}
}
3. CopyOnWriteArrayList的使用示例
public class CopyOnWriteArrayListDemo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();
// 添加元素
cowList.add("Java");
cowList.add("Python");
cowList.add("C++");
// 创建线程来修改列表
Thread threadOne = new Thread(() -> {
cowList.add("JavaScript");
cowList.remove("Python");
});
// 启动线程
threadOne.start();
// 同时进行迭代操作
Iterator<String> iterator = cowList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
try {
Thread.sleep(100); // 延迟以模拟读取操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("=============更新之后=============");
cowList.forEach(System.out::println);
}
}
在上述示例中,我们创建了一个CopyOnWriteArrayList
并在一个线程中对其进行了修改。同时,主线程中遍历了列表。由于CopyOnWriteArrayList
的特性,即使列表在遍历过程中被修改,迭代器也不会抛出ConcurrentModificationException
异常,因为迭代器是在列表被修改之前创建的。
4. CopyOnWriteArrayList的使用场景
- 读多写少的应用场景:如果应用程序中读取操作的频率远高于写入操作,
CopyOnWriteArrayList
将非常适用。 - 迭代器的一致性视图:当需要确保在迭代过程中集合不会被并发修改时,
CopyOnWriteArrayList
提供了一种简单有效的解决方案。 - 避免锁的开销:在某些场景下,使用传统的同步集合(如
Vector
或Collections.synchronizedList
)可能会因为频繁的锁操作而导致性能问题。CopyOnWriteArrayList
通过减少锁的使用提供了一种高效的替代方案。