CopyOnWriteArrayList详解

# CopyOnWriteArrayList详解

本来这个准备在并发相关的知识点整理之后再整理的,但是想想毕竟是List接口的实现,还是放在集合这块一起来整理吧。 基于JDK8.

# 1、CopyOnWriteArrayList简介

我第一次听说这个集合还是看了一个博客 说这个集合叫Cow 奶牛集合。然后就记住了哈哈。。。

     CopyOnWriteArrayList 是 List 接口的一个线程安全实现,适用于需要保证线程安全频繁读取和偶尔修改的场景。其基本工作原理是,当对列表进行写操作(如添加、删除、更新元素)时,它会创建一个底层数组的副本,然后在新数组上执行写操作。这种“写时复制”的机制确保了在进行写操作时,不会影响正在进行的读操作,从而实现了线程安全。

所以"COW" 是 "写时复制"。

# 2、如何理解"写时复制"

Copy-On-Write (COW) 概念: Copy-On-Write 是一种优化技术,主要用于提高读取性能和实现线程安全。其基本思想是在对共享数据进行修改时,并不直接修改原数据,而是首先创建原数据的一个副本,然后在副本上进行修改。这种技术广泛应用于内存管理、文件系统以及并发编程中。

工作原理

  • 共享数据:在初始状态下,多个线程共享同一个数据(如一个数组)。

  • 读操作:读取操作直接访问共享数据,不需要加锁,保证了高效性。

  • 写操作:当某个线程需要修改数据时,首先复制一份数据的副本,然后在副本上进行修改。修改完成后,将副本替换掉原有的共享数据。

优点
高效的读取:读取操作不需要加锁,可以并发执行,性能非常高。
线程安全:由于写操作是在副本上进行,不会影响其他线程的读操作,天然地实现了线程安全。
迭代安全:迭代器遍历的是数据的快照,因此在遍历期间对数据的修改不会影响迭代器的遍历。

缺点
写操作开销大:每次写操作都需要复制数据,内存消耗较大,且写操作相对较慢。 内存使用高:频繁的写操作会导致大量内存占用。

适用场景
CopyOnWriteArrayList 特别适用于读操作频繁而写操作较少的场景,例如缓存、配置管理、白名单和黑名单等。在这些场景中,读取操作占主导地位,而写操作相对较少,因此可以充分利用 Copy-On-Write 技术的优点。

# 3、CopyOnWriteArrayList的继承体系

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
mixureSecure

可以看到这个List接口的实现,实现了RandomAccess接口,说明支持快速随机访问。

# 4、CopyOnWriteArrayList的构造函数

  • ①、空参构造 volatile 关键字后面总结并发相关知识点的时候 会详细解析,这里先简单注释下功能
// 声明一个用来存储元素的数组。使用 transient 关键字表示该字段在序列化时不会被持久化。
// 使用 volatile 关键字确保对该字段的所有读写操作都能立即被所有线程看到。
private transient volatile Object[] array;

/**
 * 无参构造函数,用于创建一个空的 CopyOnWriteArrayList 实例。
 * 初始化一个空数组,并将其赋值给内部数组字段 array。
 */
public CopyOnWriteArrayList() {
    // 调用 setArray 方法,传入一个空的 Object 数组。
    setArray(new Object[0]);
}

/**
 * 设置内部数组字段 array 为指定的数组。
 * 该方法是包级私有的,且是 final 的,意味着它不能被子类重写。
 * 
 * @param a 要设置为内部数组字段的新数组
 */
final void setArray(Object[] a) {
    // 将传入的数组 a 赋值给内部数组字段 array。
    array = a;
}

可以看到CopyOnWriteArrayList的无参构造会默认初始化一个空的Object数组。

  • ②、有参构造1
    接收一个集合类型的参数
public CopyOnWriteArrayList(Collection<? extends E> c) {
    // 声明一个数组来保存元素
    Object[] elements;

    // 检查输入的集合是否是 CopyOnWriteArrayList 的实例
    if (c.getClass() == CopyOnWriteArrayList.class) {
        // 如果是,直接从提供的 CopyOnWriteArrayList 实例中获取内部数组
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    } else {
        // 否则,将集合转换为数组
        elements = c.toArray();
        
        // 检查得到的数组是否确实是 Object[] 类型
        // 这是为了处理 c.toArray() 可能返回不同类型的数组的情况  这里和ArrayList的处理是一样的
        if (elements.getClass() != Object[].class) {
            // 如果不是,创建一个包含相同元素的新 Object[] 类型数组
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
    }
    
    // 设置内部数组
    setArray(elements);
}

  • ③、有参构造2 接收一个数组类型的参数
public CopyOnWriteArrayList(E[] toCopyIn) {
    // 使用 Arrays.copyOf 方法复制传入的数组
    // 第一个参数是要复制的数组 toCopyIn
    // 第二个参数是新数组的长度,即 toCopyIn 数组的长度
    // 第三个参数是新数组的类型,这里是 Object[].class
    // 该方法返回一个新的 Object[] 类型的数组,包含了 toCopyIn 数组中的所有元素
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

可以看到这里的初始化并没有扩容或者对于数组容量方面的处理。在这些构造函数中,传入的数组直接被复制为一个新的 Object[] 数组,没有进行额外的扩容处理。这意味着集合的初始容量就是传入数组的长度,不会为未来的添加操作预留额外的空间。
这也侧面印证了 这个集合的设计目的,应对读多写少的场景。

# 5、CopyOnWriteArrayList的使用示例

这个例子能体现出CopyOnWriteArrayList的特点。 模拟多线程的读写。新建3个读线程,每个线程读5次。同时启动一个写线程。

import java.util.concurrent.CopyOnWriteArrayList;

public class TestA {
    public static void main(String[] args) throws Exception {
        // 创建一个 CopyOnWriteArrayList 实例
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

        // 初始化列表
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }

        // 创建并启动多个读线程
        Thread reader1 = new Thread(new ReaderTask(list), "Reader-1");
        Thread reader2 = new Thread(new ReaderTask(list), "Reader-2");
        Thread reader3 = new Thread(new ReaderTask(list), "Reader-3");

        reader1.start();
        reader2.start();
        reader3.start();

        // 创建并启动一个写线程
        Thread writer = new Thread(new WriterTask(list), "Writer");
        writer.start();

        // 等待所有线程完成
        reader1.join();
        reader2.join();
        reader3.join();
        writer.join();

        System.out.println("Final list: " + list);
    }
}

// 读任务
class ReaderTask implements Runnable {
    private CopyOnWriteArrayList<Integer> list;

    public ReaderTask(CopyOnWriteArrayList<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - List: " + list);
            try {
                // 模拟读取过程中的延迟
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

// 写任务
class WriterTask implements Runnable {
    private CopyOnWriteArrayList<Integer> list;

    public WriterTask(CopyOnWriteArrayList<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        for (int i = 10; i < 15; i++) {
            list.add(i);
            System.out.println(Thread.currentThread().getName() + " - Added: " + i);
            try {
                // 模拟写入过程中的延迟
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

结果:


Reader-1 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Reader-2 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Reader-3 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Writer - Added: 10
Reader-1 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Reader-2 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Reader-3 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Writer - Added: 11
Reader-2 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Reader-3 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Reader-1 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Reader-3 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Reader-2 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Reader-1 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Writer - Added: 12
Reader-3 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Reader-1 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Reader-2 - List: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
Writer - Added: 13
Writer - Added: 14
Final list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

从结果可以看出,CopyOnWriteArrayList在多线程下的写操作,并不会影响并发读。而且最新一次的读操作也能读到数组新插入的元素。

这里读线程能读取到写线程的更改,实际上是下一次的读取能够读取到最新的更改,但是本次的读取是读取的当前状态下的数据。

我们再来看个例子:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class TestA {
    
    public static void main(String[] args) throws Exception {
        // 创建一个 CopyOnWriteArrayList 实例
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

        // 初始化列表
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }

        // 创建并启动一个读线程
        Thread reader = new Thread(() -> {
            Iterator<Integer> iterator = list.iterator();
            while (iterator.hasNext()) {
                Integer value = iterator.next();
                System.out.println(Thread.currentThread().getName() + " - Value: " + value);
                try {
                    // 模拟遍历过程中的延迟
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }, "Reader");

        // 创建并启动一个写线程
        Thread writer = new Thread(() -> {
            try {
                // 模拟写入操作的延迟
                Thread.sleep(500);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            list.add(10);
            System.out.println(Thread.currentThread().getName() + " - Added: " + 10);
        }, "Writer");

        reader.start();
        writer.start();

        reader.join();
        writer.join();

        System.out.println("Final list: " + list);
    }

}

运行结果:

Reader - Value: 0
Reader - Value: 1
Reader - Value: 2
Reader - Value: 3
Reader - Value: 4
Writer - Added: 10
Reader - Value: 5
Reader - Value: 6
Reader - Value: 7
Reader - Value: 8
Reader - Value: 9
Final list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

从这个结果中可以看到,写操作在读操作 读到4的时候就把10写入到集合了,但是读操作最终没读到10。
但是最新一次的主线程遍历又读到了10。
这说明CopyOnWriteArrayList,实际上是弱一致性的。

# 6、CopyOnWriteArrayList 的 add方法

新增方法有三种:

  • ①、add(E e)
    将元素 e 添加到列表的末尾。
    线程安全地进行添加操作,通过复制底层数组实现。

  • ②、add(int index, E element)
    在指定位置 index 插入元素 element。
    插入位置及其之后的元素向后移动。
    线程安全地进行插入操作,通过复制底层数组实现。

  • ③、addIfAbsent(E e)
    如果元素 e 不在列表中,则将其添加到列表的末尾。
    线程安全地进行添加操作,通过复制底层数组实现。

add(E e) 方法详细注释:

// 创建 ReentrantLock 实例 
final transient ReentrantLock lock = new ReentrantLock();

public boolean add(E e) {
    // 获取 ReentrantLock锁实例
    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);

        // 返回 true,表示元素成功添加
        return true;
    } finally {
        // 确保在退出方法之前释放锁
        lock.unlock();
    }
}

// 获取当前内部数组的引用
final Object[] getArray() {
        return array;
    }
 
// 设置内部数组 array 为 新的数组a
 final void setArray(Object[] a) {
        array = a;
    }

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;
        
        // 检查索引是否超出范围
        // 如果索引大于当前数组长度或者小于0,则抛出 IndexOutOfBoundsException
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + len);
        
        // 声明新数组的引用
        Object[] newElements;
        
        // 计算从插入点到数组末尾的元素数量
        int numMoved = len - index;
        
        // 如果插入点在数组末尾
        if (numMoved == 0)
            // 创建一个新数组,其长度为当前数组长度加1,并复制当前数组的所有元素
            newElements = Arrays.copyOf(elements, len + 1);
        else {
            // 否则,创建一个新数组,其长度为当前数组长度加1
            newElements = new Object[len + 1];
            
            // 将当前数组的前半部分(插入点之前的元素)复制到新数组中
            System.arraycopy(elements, 0, newElements, 0, index);
            
            // 将当前数组的后半部分(插入点及其之后的元素)复制到新数组中,从插入点的下一个位置开始
            System.arraycopy(elements, index, newElements, index + 1, numMoved);
        }
        
        // 在新数组的插入点位置添加新元素
        newElements[index] = element;
        
        // 用新数组替换内部数组
        setArray(newElements);
    } finally {
        // 确保在退出方法之前释放锁
        lock.unlock();
    }
}

addIfAbsent(E e) 方法详细注释:

public boolean addIfAbsent(E e) {
    // 获取当前内部数组的快照
    Object[] snapshot = getArray();
    
    // 检查元素 e 是否存在于快照中,如果存在则返回 false;否则尝试将 e 添加到列表中
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}

private static int indexOf(Object o, Object[] elements,
                           int index, int fence) {
    // 检查对象是否为 null
    if (o == null) {
        // 遍历元素数组,找到第一个为 null 的位置
        for (int i = index; i < fence; i++)
            if (elements[i] == null)
                return i;
    } else {
        // 遍历元素数组,找到第一个与 o 相等的位置
        for (int i = index; i < fence; i++)
            if (o.equals(elements[i]))
                return i;
    }
    // 如果未找到匹配的元素,返回 -1
    return -1;
}

private boolean addIfAbsent(E e, Object[] snapshot) {
    // 获取当前类的 ReentrantLock 锁实例
    final ReentrantLock lock = this.lock;
    // 获取锁,确保线程安全
    lock.lock();
    try {
        // 获取当前内部数组的引用
        Object[] current = getArray();
        // 获取当前数组的长度
        int len = current.length;
        
        // 检查快照是否与当前数组相同
        if (snapshot != current) {
            // 优化:处理与其他 addXXX 操作竞争的情况
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            // 检查元素是否在当前数组中存在
            if (indexOf(e, current, common, len) >= 0)
                return false;
        }
        
        // 创建一个新数组,其长度为当前数组长度加 1,并复制当前数组的所有元素
        Object[] newElements = Arrays.copyOf(current, len + 1);
        // 在新数组的末尾添加新元素
        newElements[len] = e;
        // 用新数组替换内部数组
        setArray(newElements);
        // 返回 true,表示元素成功添加
        return true;
    } finally {
        // 确保在退出方法之前释放锁
        lock.unlock();
    }
}

通过源码
// 创建 ReentrantLock 实例 final transient ReentrantLock lock = new ReentrantLock();
final ReentrantLock lock = this.lock;

使用final修饰 保证 lock 引用不可被修改,通过ReentrantLock 可重入锁 ,实现添加方法的线程安全。 通过:Object[] newElements = Arrays.copyOf(current, len + 1); 创建一个新的数组,其长度为当前数组长度加 1,并复制当前数组的所有元素 。 添加操作实际上是把元素添加到了这个新复制的数组里了,这就体现了COW写时复制的思想。
最后再 setArray(newElements);用新数组替换内部数组。

注意点: CopyOnWriteArrayList并没有size属性,因为CopyOnWriteArrayList没有扩容机制,其内部数组的length就是实际的CopyOnWriteArrayList的大小。 所以CopyOnWriteArrayList的size()方法就是返回内部数组的length即可。

public int size() {
        return getArray().length;
    }

# 7、CopyOnWriteArrayList弱一致性的体现

若一致性是制一致性约束较为宽松,某些情况下允许存在短暂的不一致性。

主要体现在下面几个方面:

  • ①、“写时复制”,即每次对列表进行修改(如添加、删除、更新)时,都会创建该列表的一个新副本。原列表在修改过程中不会被改变。这种创建快照的形式意味着所有的读操作都将在旧的、不变的数组上进行,而修改操作将创建一个新的数组副本并替换旧数组。由于读操作不需要加锁,读操作可能不会立即看到最新的写操作结果。

  • ②、并发读取时,可能会有线程看到旧的数组快照,而另一个线程看到新的数组快照。因为读操作不需要加锁。

  • ③、修改的延迟可见,对于CopyOnWriteArrayList的增、删、改方法。 拿新增方法举例:

 // 在新数组的末尾添加新元素
 newElements[len] = e;
 // 用新数组替换内部数组
 setArray(newElements);

元素新增后还要调用 setArray 把内部数组的引用指向新数组,才算修改操作真正完成。在setArray(newElements);方法执行之前,其他读取线程依旧访问的是旧的数组引用,即便新元素已经添加到了新数组中。直到 setArray(newElements); 方法执行完毕,其他线程才能看到最新的修改。

# 8、CopyOnWriteArrayList的remove方法

CopyOnWriteArrayList删除元素:

remove(int index):删除指定位置上的元素。 boolean remove(Object o):删除此首次出现的指定元素,如果不存在该元素则返回 false。 boolean removeAll(Collection<?> c):删除指定集合中的全部元素。

E remove(int index)方法:

public E remove(int index) {
    // 获取ReentrantLock锁对象,以确保线程安全
    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)
            // 创建一个新数组,长度为旧数组长度减1,并将其设置为内部数组
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            // 创建一个新数组,长度为旧数组长度减1
            Object[] newElements = new Object[len - 1];
            // 将旧数组从起始位置到指定索引位置的元素复制到新数组
            System.arraycopy(elements, 0, newElements, 0, index);
            // 将旧数组从指定索引位置之后的元素复制到新数组
            System.arraycopy(elements, index + 1, newElements, index, numMoved);
            // 用新数组替换内部数组
            setArray(newElements);
        }
        // 返回被移除的旧值
        return oldValue;
    } finally {
        // 确保锁在方法结束时释放,以避免死锁
        lock.unlock();
    }
}

boolean remove(Object o)方法:

public boolean remove(Object o) {
    // 获取当前数组的快照
    Object[] snapshot = getArray();
    // 查找对象o在数组中的索引
    int index = indexOf(o, snapshot, 0, snapshot.length);
    // 如果索引小于0(未找到),返回false;否则,调用remove方法移除该元素
    return (index < 0) ? false : remove(o, snapshot, index);
}

private static int indexOf(Object o, Object[] elements, int index, int fence) {
    // 如果对象o是null,寻找第一个null元素的索引
    if (o == null) {
        for (int i = index; i < fence; i++)
            if (elements[i] == null)
                return i;
    } else {
        // 如果对象o不是null,寻找第一个与o相等的元素的索引
        for (int i = index; i < fence; i++)
            if (o.equals(elements[i]))
                return i;
    }
    // 如果未找到,返回-1
    return -1;
}

private boolean remove(Object o, Object[] snapshot, int index) {
    // 获取ReentrantLock锁对象,以确保线程安全
    final ReentrantLock lock = this.lock;
    // 锁定,确保在该方法执行期间其他线程无法修改数组
    lock.lock();
    try {
        // 获取当前数组
        Object[] current = getArray();
        // 获取当前数组的长度
        int len = current.length;
        // 检查当前数组和快照是否相同
        if (snapshot != current) findIndex: {
            // 计算索引和长度的较小值
            int prefix = Math.min(index, len);
            // 重新定位索引,寻找匹配的元素
            for (int i = 0; i < prefix; i++) {
                if (current[i] != snapshot[i] && eq(o, current[i])) {
                    index = i;
                    break findIndex;
                }
            }
            // 如果索引超出数组长度,返回false
            if (index >= len)
                return false;
            // 如果当前索引位置的元素匹配,跳出查找
            if (current[index] == o)
                break findIndex;
            // 重新查找对象o在当前数组中的索引
            index = indexOf(o, current, index, len);
            // 如果未找到,返回false
            if (index < 0)
                return false;
        }
        // 创建一个新数组,长度为旧数组长度减1
        Object[] newElements = new Object[len - 1];
        // 将旧数组从起始位置到指定索引位置的元素复制到新数组
        System.arraycopy(current, 0, newElements, 0, index);
        // 将旧数组从指定索引位置之后的元素复制到新数组
        System.arraycopy(current, index + 1, newElements, index, len - index - 1);
        // 用新数组替换内部数组
        setArray(newElements);
        // 返回true表示成功移除元素
        return true;
    } finally {
        // 确保锁在方法结束时释放,以避免死锁
        lock.unlock();
    }
}

过程总结:

  • ①、remove(Object o) 方法:
    获取当前数组的快照。 查找对象 o 在快照中的索引。 如果未找到(索引小于0),返回 false。 否则,调用 remove(o, snapshot, index) 方法进行移除操作。

  • ②、indexOf(Object o, Object[] elements, int index, int fence) 方法:
    遍历数组,从指定索引到指定范围(fence)寻找对象 o 的索引。 如果对象 o 是 null,寻找第一个 null 元素的索引。 否则,寻找第一个与 o 相等的元素的索引。 如果未找到,返回 -1。

  • ③、remove(Object o, Object[] snapshot, int index) 方法:

获取锁对象,确保线程安全。 获取当前数组及其长度。 如果当前数组与快照不同,重新定位索引,寻找匹配的元素。 计算前缀长度。 遍历前缀部分,重新定位索引,寻找匹配的元素。 如果索引超出数组长度,返回 false。 如果当前索引位置的元素匹配,跳出查找。 重新查找对象 o 在当前数组中的索引。 如果未找到,返回 false。 创建一个新数组,长度为旧数组长度减1。 将旧数组从起始位置到指定索引位置的元素复制到新数组。 将旧数组从指定索引位置之后的元素复制到新数组。 用新数组替换内部数组。 返回 true 表示成功移除元素。

removeAll(Collection<?> c)方法:

public boolean removeAll(Collection<?> c) {
    // 如果集合c为null,抛出NullPointerException
    if (c == null) throw new NullPointerException();
    
    // 获取ReentrantLock锁对象,以确保线程安全
    final ReentrantLock lock = this.lock;
    // 锁定,确保在该方法执行期间其他线程无法修改数组
    lock.lock();
    try {
        // 获取当前数组的副本
        Object[] elements = getArray();
        // 获取当前数组的长度
        int len = elements.length;
        // 如果数组不为空
        if (len != 0) {
            // 临时数组,用于保存需要保留的元素
            int newlen = 0;
            Object[] temp = new Object[len];
            // 遍历当前数组的所有元素
            for (int i = 0; i < len; ++i) {
                Object element = elements[i];
                // 如果集合c不包含当前元素,将其保存到临时数组中
                if (!c.contains(element))
                    temp[newlen++] = element;
            }
            // 如果新长度和旧长度不相等,说明有元素被移除
            if (newlen != len) {
                // 创建一个新的数组,仅包含需要保留的元素,并将其设置为内部数组
                setArray(Arrays.copyOf(temp, newlen));
                // 返回true,表示成功移除元素
                return true;
            }
        }
        // 返回false,表示没有元素被移除
        return false;
    } finally {
        // 确保锁在方法结束时释放,以避免死锁
        lock.unlock();
    }
}

还有一个删除全部元素的方法clear()

public void clear() {
    // 获取ReentrantLock锁对象,以确保线程安全
    final ReentrantLock lock = this.lock;
    // 锁定,确保在该方法执行期间其他线程无法修改数组
    lock.lock();
    try {
        // 设置一个新的空数组,清空当前列表
        setArray(new Object[0]);
    } finally {
        // 确保锁在方法结束时释放,以避免死锁
        lock.unlock();
    }
}