CopyOnWriteArrayList 是 Java 中一种线程安全的 List 实现,它通过在每次修改时复制底层数组来实现线程安全。本文将详细分析 CopyOnWriteArrayList 的源码,并结合具体使用案例来展示其使用场景和优缺点。
一、CopyOnWriteArrayList 简介
CopyOnWriteArrayList 是 java.util.concurrent 包中的一个类,适用于读多写少的场景。它的主要特点是:
1. 线程安全:通过复制底层数组来实现线程安全。
2. 读操作无锁:读操作不需要加锁,性能较高。
3. 写操作开销大:每次写操作都会复制整个数组,开销较大。
二、CopyOnWriteArrayList 源码分析
1. 类定义
CopyOnWriteArrayList 继承自 AbstractList 并实现了 List 接口:
public class CopyOnWriteArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
// 底层数组
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
}
2. 构造方法
CopyOnWriteArrayList 提供了多种构造方法:
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
3. 读操作
读操作不需要加锁,直接读取底层数组:
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
private E get(Object[] a, int index) {
return (E) a[index];
}
4. 写操作
写操作需要加锁,并在修改时复制底层数组:
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();
}
}
final void setArray(Object[] a) {
array = a;
}
5. 迭代器
CopyOnWriteArrayList 的迭代器是弱一致性的,不会抛出 ConcurrentModificationException:
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
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 E next() {
if (!hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
}
三、使用案例
1. 基本使用
以下是一个基本的使用示例,展示了如何创建和操作 CopyOnWriteArrayList:
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 添加元素
list.add("A");
list.add("B");
list.add("C");
// 读取元素
System.out.println("元素: " + list.get(0));
// 迭代元素
for (String s : list) {
System.out.println("迭代元素: " + s);
}
// 删除元素
list.remove("B");
System.out.println("删除后元素: " + list);
}
}
2. 多线程环境下的使用
CopyOnWriteArrayList 适用于多线程环境,以下示例展示了在多线程环境下的使用:
import java.util.concurrent.CopyOnWriteArrayList;
public class MultiThreadExample {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
// 创建多个线程进行读写操作
Thread writer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
list.add(i);
System.out.println("添加元素: " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread reader = new Thread(() -> {
for (int i = 0; i < 10; i++) {
if (!list.isEmpty()) {
System.out.println("读取元素: " + list.get(0));
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
writer.start();
reader.start();
try {
writer.join();
reader.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3. 使用场景
CopyOnWriteArrayList 适用于以下场景:
1. 读多写少:在读操作远多于写操作的场景中,CopyOnWriteArrayList 提供了高效的读操作。
2. 迭代器弱一致性:在需要迭代器弱一致性的场景中,CopyOnWriteArrayList 的迭代器不会抛出 ConcurrentModificationException。
4. 优缺点
优点:
- 线程安全:通过复制底层数组实现线程安全。
2. 读操作高效:读操作不需要加锁,性能较高。
- 迭代器弱一致性:迭代器不会抛出 ConcurrentModificationException。
缺点:
- 写操作开销大:每次写操作都会复制整个数组,开销较大。
- 内存占用高:频繁的写操作会导致内存占用较高。
四、总结
CopyOnWriteArrayList 是一种适用于读多写少场景的线程安全 List 实现。通过分析其源码,我们可以看到它通过复制底层数组来实现线程安全,读操作不需要加锁,写操作需要加锁并复制数组。结合具体使用案例,我们展示了 CopyOnWriteArrayList 在多线程环境下的使用方法及其优缺点。理解 CopyOnWriteArrayList 的工作原理和使用场景,有助于我们在实际开发中选择合适的数据结构来提高程序的性能和可靠性。