HashMap对应的线程安全类ConcurrentHashMap,本文来学习ArrayList对应的线程安全类CopyOnwriteArrayList。
认识CopyOnWriteArrayList
CopyOnWriteArrayList是java.util.concurrent中的一个线程安全的List实现。它使用“写时复制”(Copy-On-Write)的策略,即在修改操作(如添加、设置、删除元素)时,复制一份当前数组进行修改,而不是直接在原数组上修改。这样可以保证读操作无需加锁,且不会读取到正在修改的数组,从而实现了线程安全。
/**
* A thread-safe variant of {@link java.util.ArrayList} in which all mutative
* operations ({@code add}, {@code set}, and so on) are implemented by
* making a fresh copy of the underlying array.
*
* <p>This is ordinarily too costly, but may be <em>more</em> efficient
* than alternatives when traversal operations vastly outnumber
* mutations, and is useful when you cannot or don't want to
* synchronize traversals, yet need to preclude interference among
* concurrent threads. The "snapshot" style iterator method uses a
* reference to the state of the array at the point that the iterator
* was created. This array never changes during the lifetime of the
* iterator, so interference is impossible and the iterator is
* guaranteed not to throw {@code ConcurrentModificationException}.
* The iterator will not reflect additions, removals, or changes to
* the list since the iterator was created. Element-changing
* operations on iterators themselves ({@code remove}, {@code set}, and
* {@code add}) are not supported. These methods throw
* {@code UnsupportedOperationException}.
*
* <p>All elements are permitted, including {@code null}.
*
* <p>Memory consistency effects: As with other concurrent
* collections, actions in a thread prior to placing an object into a
* {@code CopyOnWriteArrayList}
* <a href=" "><i>happen-before</i></a >
* actions subsequent to the access or removal of that element from
* the {@code CopyOnWriteArrayList} in another thread.
*
* <p>This class is a member of the
* <a href="{@docRoot}/java.base/java/util/package-summary.html#CollectionsFramework">
* Java Collections Framework</a >.
*
* @since 1.5
* @author Doug Lea
* @param <E> the type of elements held in this list
*/
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
//......
}
CopyOnWriteArrayList是一种线程安全的java.util.ArrayList 变体,其中所有的修改操作(add、set等)都是通过创建底层数组的一个新副本来实现的。
通常这种做法代价较高,但当遍历操作远多于修改操作时,可能比其他替代方案更高效。当你无法或不想对遍历操作进行同步,但又需要防止并发线程之间的干扰时,这种实现非常有用。“快照”风格的迭代器方法使用在创建迭代器时数组状态的引用。在迭代器的生命周期内,这个数组永远不会改变,因此不可能发生干扰,且保证迭代器不会抛出 ConcurrentModificationException。迭代器不会反映自创建以来对列表进行的添加、删除或更改操作。不支持对迭代器本身进行更改元素的操作(remove、set和add)。这些方法会抛出UnsupportedOperationException。
允许所有元素,包括null。
内存一致性影响:与其他并发集合一样,在一个线程将对象存入CopyOnWriteArrayList之前的操作,先发生于(happen-before)另一个线程从CopyOnWriteArrayList 中访问或删除该元素之后的操作。
CopyOnWriteArrayList核心API
构造函数

(1)无参构造函数
......
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
......
/**
* Creates an empty list.
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
/**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}
创建一个空的 CopyOnWriteArrayList。从源码可以看出,setArray 方法用于设置底层存储数据的数组,这里初始化为一个长度为 0 的 Object 数组。
(2)参数为集合的构造函数
/**
* Creates a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection of initially held elements
* @throws NullPointerException if the specified collection is null
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] es;
if (c.getClass() == CopyOnWriteArrayList.class)
es = ((CopyOnWriteArrayList<?>)c).getArray();
else {
es = c.toArray();
if (c.getClass() != java.util.ArrayList.class)
es = Arrays.copyOf(es, es.length, Object[].class);
}
setArray(es);
}
使用给定集合中的元素初始化 CopyOnWriteArrayList。如果给定的集合本身就是 CopyOnWriteArrayList 类型,则直接复制其内部数组。否则,调用集合的 toArray 方法将其元素复制到一个新的数组中。
如果 toArray 返回的数组类型不是 Object[],则使用 Arrays.copyOf 方法将其复制到一个新的 Object[] 数组中。最后,调用 setArray 方法将新创建的数组设置为内部存储数组。
(3)参数为数组的构造函数
/**
* Creates a list holding a copy of the given array.
*
* @param toCopyIn the array (a copy of this array is used as the
* internal array)
* @throws NullPointerException if the specified array is null
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
使用给定数组中的元素初始化 CopyOnWriteArrayList,通过复制数组来保证原始数组不会被修改。使用 Arrays.copyOf 方法将给定的数组复制到一个新的 Object[] 数组中。调用 setArray 方法将新创建的数组设置为内部存储数组。
添加元素
CopyOnWriteArrayList内部提供了多个添加元素的方法。

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
从jdk21的源码中可以看出,add(E e)方法内部通过synchronized锁机制(jdk1.8的时候还是用ReentrantLock)来确保同一时间只有一个线程可以执行写操作:
- 在添加元素之前,CopyOnWriteArrayList 会先获取当前的底层数组的快照,这个复制操作是通过 Arrays.copyOf 方法完成,生成一个新的数组,其长度比原数组大 1。遵循“写时复制”(Copy-On-Write)的策略。
- 在新复制的数组上,将新元素添加到数组的末尾。
- 将底层数组的引用指向新创建的数组,这样后续的读操作就会看到新添加的元素。
除了add(E e)方法,还提供了在数组特定位置添加元素add(int index, E element)、在数组头部添加元素addFirst(E e)、在数组尾部添加元素addLast(E e) 相关方法。
删除元素
CopyOnWriteArrayList内部同样提供了多个删除元素的方法。
/**
* 删除指定索引处的元素,并返回被删除的元素。
*
* @param index 要删除的元素的索引
* @return 被删除的元素
* @throws IndexOutOfBoundsException 如果索引超出范围(index < 0 || index >= size())
*/
public E remove(int index) {
// 使用同步锁(lock)保证线程安全,同一时间只允许一个线程执行写操作
synchronized (lock) {
// 获取当前底层数组的快照
Object[] es = getArray();
int len = es.length; // 当前数组长度
// 获取要删除的元素(通过辅助方法 elementAt),供后续返回
E oldValue = elementAt(es, index);
// 计算需要移动的元素数量:删除位置之后的元素个数
int numMoved = len - index - 1;
Object[] newElements; // 定义新数组,用于存储删除后的元素
// 如果删除的是最后一个元素,直接复制前 len-1 个元素到新数组
if (numMoved == 0) {
newElements = Arrays.copyOf(es, len - 1);
}
// 否则,需要分两步复制:
// 1. 复制删除位置之前的元素
// 2. 复制删除位置之后的元素
else {
newElements = new Object[len - 1]; // 创建新数组,长度减1
// 复制删除位置之前的元素(从原数组的 0 到 index-1)
System.arraycopy(es, 0, newElements, 0, index);
// 复制删除位置之后的元素(从原数组的 index+1 开始,复制到新数组的 index 位置)
System.arraycopy(es, index + 1, newElements, index, numMoved);
}
// 将底层数组的引用指向新数组,使后续操作基于新数组
setArray(newElements);
// 返回被删除的元素
return oldValue;
}
}
该方法是按索引删除元素,删除操作是写操作,同样需要加锁来确保同一时间只有一个线程能修改列表,避免数据竞争:
- 在加锁后,CopyOnWriteArrayList 会先获取当前底层数组的快照。通用遵循“写时复制”(Copy-On-Write)的策略。
- 复制操作通过 Arrays.copyOf方法完成,生成一个与原数组内容相同的新数组。
- 在新数组上执行删除操作。
- 将底层数组的引用指向新数组,使后续的读操作看到删除后的结果。
除了remove(int index)方法,还提供了删除数组头部元素removeFirst()、删除数组尾部元素removeLast()、按对象删除元素remove(Object o)、按索引范围删除元素removeRange(int fromIndex, int toIndex)等相关方法。
遍历元素
CopyOnWriteArrayList 的遍历操作基于“快照”机制,实现线程安全与弱一致性。遍历时直接访问底层数组的当前快照,无需加锁。遍历过程中,若其他线程修改列表(如添加/删除元素),迭代器不会感知变化,仍基于创建时的快照返回数据。遍历元素的方式主要有三种:
(1)通过迭代器遍历
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator provides a snapshot of the state of the list
* when the iterator was constructed. No synchronization is needed while
* traversing the iterator. The iterator does <em>NOT</em> support the
* {@code remove} method.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
迭代器创建时锁定当前数组快照,后续遍历不受写操作影响;迭代过程无需加锁且不支持删除元素操作。遍历过程中若列表被修改,迭代器不会抛出 ConcurrentModificationException。
示例代码:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// 获取迭代器(基于当前数组快照)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
// 输出:A, B, C(顺序固定,不反映后续修改)
System.out.println(item);
}
(2)通过 forEach 遍历
/**
* @throws NullPointerException {@inheritDoc}
*/
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
for (Object x : getArray()) {
@SuppressWarnings("unchecked") E e = (E) x;
action.accept(e);
}
}
示例代码:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.forEach(System.out::println);
list.forEach(item -> System.out.println(item));
(3)通过 get(int index) 遍历
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return elementAt(getArray(), index);
}
示例代码:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
for(String item : list){
System.out.println(item);
}
for (int i = 0; i < list.size(); i++) {
// 通过索引直接访问数组
System.out.println(list.get(i));
}
与ArrayList 性能对比
以下是一个多线程并发读写测试代码,用于对比 CopyOnWriteArrayList 和 ArrayList 在并发环境下的性能和安全性差异。代码通过模拟多个线程同时读写列表,观察两者的表现。
public class CopyOnWriteArrayListTest {
private static final int THREAD_COUNT = 20; // 线程数
private static final int OPERATION_COUNT = 10000; // 每个线程的操作数
private static final Random RANDOM = new Random();
// 测试 CopyOnWriteArrayList
private static class CopyOnWriteTest implements Runnable {
private CopyOnWriteArrayList<Integer> list;
private AtomicInteger successCount;
public CopyOnWriteTest(CopyOnWriteArrayList<Integer> list, AtomicInteger successCount) {
this.list = list;
this.successCount = successCount;
}
@Override
public void run() {
for (int i = 0; i < OPERATION_COUNT; i++) {
try {
if (RANDOM.nextBoolean()) {
list.add(RANDOM.nextInt(1000)); // 随机添加
}
successCount.incrementAndGet(); // 写成功计数
} catch (Exception e) {
// 忽略失败(理论上 CopyOnWriteArrayList 不会失败)
System.out.println("CopyOnWriteArrayList写失败:" + e.getMessage());
}
}
}
public int getSuccessCount() {
return successCount.get();
}
}
// 测试普通 ArrayList(非线程安全)
private static class ArrayListTest implements Runnable {
private ArrayList<Integer> list;
private AtomicInteger successCount;
public ArrayListTest(ArrayList<Integer> list, AtomicInteger successCount) {
this.list = list;
this.successCount = successCount;
}
@Override
public void run() {
for (int i = 0; i < OPERATION_COUNT; i++) {
try {
if (RANDOM.nextBoolean()) {
list.add(RANDOM.nextInt(1000));
}
successCount.incrementAndGet();
} catch (Exception e) {
// 捕获并发修改异常(如 ConcurrentModificationException)
System.out.println("ArrayList写失败:" + e.getMessage());
}
}
}
public int getSuccessCount() {
return successCount.get();
}
}
public static void main(String[] args) throws InterruptedException {
// 测试 CopyOnWriteArrayList写
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
testWrite(list, "CopyOnWriteArrayList");
testRead(list, "CopyOnWriteArrayList");
// 测试 ArrayList写
ArrayList<Integer> list2 = new ArrayList<>();
testWrite(list2, "ArrayList");
testRead(list2, "ArrayList");
}
private static void testRead(List<Integer> list, String listType) throws InterruptedException {
AtomicInteger successCount = new AtomicInteger(0);
Thread[] threadArray1 = new Thread[THREAD_COUNT];
long startTime = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
threadArray1[i] = new Thread(() -> {
if (!list.isEmpty()) {
for (int j = 0; j < OPERATION_COUNT; j++) {
list.get(RANDOM.nextInt(list.size()));
successCount.incrementAndGet();
}
}
});
threadArray1[i].start();
}
for (Thread t : threadArray1) {
t.join();
}
long endTime = System.currentTimeMillis();
System.out.println(listType + "测试完成,读成功操作数: " + successCount.get() + ",耗时:" + (endTime - startTime));
}
private static void testWrite(List<Integer> list, String listType) throws InterruptedException {
AtomicInteger successCount = new AtomicInteger(0);
Thread[] threadArray1 = new Thread[THREAD_COUNT];
long startTime = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
if ("CopyOnWriteArrayList".equals(listType)) {
CopyOnWriteTest copyOnWriteTask = new CopyOnWriteTest((CopyOnWriteArrayList<Integer>) list, successCount);
threadArray1[i] = new Thread(copyOnWriteTask);
threadArray1[i].start();
}
if ("ArrayList".equals(listType)) {
ArrayListTest arrayListTask = new ArrayListTest((ArrayList<Integer>) list, successCount);
threadArray1[i] = new Thread(arrayListTask);
threadArray1[i].start();
}
}
for (Thread t : threadArray1) {
t.join();
}
long endTime = System.currentTimeMillis();
System.out.println(listType + "测试完成,写成功操作数: " + successCount.get() + ",耗时:" + (endTime - startTime));
}
}
运行结果:
CopyOnWriteArrayList测试完成,写成功操作数: 200000,耗时:3288
CopyOnWriteArrayList测试完成,读成功操作数: 200000,耗时:19
ArrayList写失败:Index 302 out of bounds for length 244
ArrayList测试完成,写成功操作数: 199999,耗时:28
ArrayList测试完成,读成功操作数: 200000,耗时:18
从运行结果可以看出,CopyOnWriteArrayList在读写性能上并不具备优势,而是在线程安全方面保证了数据的准确性。
461

被折叠的 条评论
为什么被折叠?



