CopyOnWriteArrayList 源码分析、优缺点及典型使用场景

首先,CopyOnWriteArrayList是Java并发包中的一个线程安全的List实现,适用于读多写少的场景。
它的核心思想是写时复制,也就是在修改列表时,会复制一个新的数组,修改完成后再替换原来的数组。
这样读操作不需要加锁,可以保证高并发读的性能。

接下来,源码结构。比如,内部使用了一个volatile数组来存储数据,
写操作时通过ReentrantLock保证线程安全,然后复制数组进行修改。
需要详细解释关键方法的实现,比如add、remove、iterator等。比如,add方法会加锁
,复制数组,添加元素,然后设置新数组。迭代器是直接基于当前数组的快照,不会反映后续的修改。

然后是优缺点。优点肯定是读操作的高性能,无锁,线程安全。
缺点就是写操作开销大,每次修改都要复制整个数组,内存占用高,数据实时性差,
因为读的是旧数组的快照。所以适用于读多写少,比如监听器列表、配置信息的存储等场景。

用户可能是在设计一个高并发的系统,需要选择合适的集合类。可能遇到的困惑是,
在需要频繁写入的情况下使用CopyOnWriteArrayList会导致性能问题。
所以需要强调适用场景,避免误用。另外,用户可能对迭代器的行为不太清楚,
比如迭代过程中不会抛出ConcurrentModificationException,但也不会看到最新的修改。

和Collections.synchronizedList的区别,
或者与ConcurrentLinkedQueue等其他并发容器的比较。

总结回答结构:先介绍类的作用,然后源码解析,优缺点,使用场景,最后可能的最佳实践或者注意事项。

CopyOnWriteArrayList 是 Java 并发包(java.util.concurrent)中提供的线程安全的 List 实现,其核心思想是 写时复制。以下是其源码分析、优缺点及典型使用场景。


一、源码关键实现

1. 核心数据结构
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    // 使用ReentrantLock实现独占锁,确保写操作的原子性
    final transient ReentrantLock lock = new ReentrantLock();

    // 使用volatile修饰,确保多线程环境下对array的可见性
    private transient volatile Object[] array;

    // 获取array的引用
    final Object[] getArray() {
        return array;
    }

    // 设置array的引用
    final void setArray(Object[] a) {
        array = a;
    }

    // 无参构造器,初始化为一个空数组
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    // 接收一个Collection作为参数,将其元素复制到本List中
    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);
    }

    // 接收一个数组作为参数,将其元素复制到本List中
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
}
2. 写操作(add/remove/set)

以 add 方法为例:

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();
    }
}
  • 所有写操作均加锁,保证线程安全。

  • 复制数组:每次修改都会创建一个新数组,替换旧数组。

3. 读操作(get/size/iterator)
public E get(int index) {
    return get(getArray(), index); // 直接读取当前数组,无需加锁
}

private E get(Object[] a, int index) {
    return (E) a[index];
}

// 迭代器基于当前数组的快照,不会感知后续修改
public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}
  • 读操作无锁,直接访问当前数组。

  • 弱一致性迭代器:迭代器遍历的是操作发生时的数组快照。

4. 删除操作(remove)

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;
        if (numMoved == 0) {
            setArray(Arrays.copyOf(elements, len - 1));
        } else {
            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();
    }
}

二、优缺点分析

优点
  1. 读性能极高
    读操作无需加锁,适合读多写少的场景(如监听器列表、配置管理)。

  2. 线程安全
    通过写时复制和锁机制保证线程安全,简化了并发编程的复杂性。

  3. 避免 ConcurrentModificationException
    提供了一个弱一致性的迭代器,在迭代过程中,即使列表发生了修改,迭代器仍然可以安全地进行,不会抛出ConcurrentModificationException异常。(迭代器基于快照,遍历时即使发生写操作也不会抛出异常。)

  4. 适合读多写少的场景:由于读操作无锁且非常高效,适用于读操作频繁而写操作较少的场景。
缺点
  1. 内存开销大
    由于每次操作都会创建底层数组的一个副本,因此内存开销相对较大。特别是在列表包含大量元素时,每次写操作都需要复制整个数组,这不仅会增加内存消耗,还可能导致垃圾回收频繁发生,影响系统性能。

  2. 写性能差: 由于每次写操作都需要复制整个底层数组,时间复杂度是O(n),其中n是数组的大小。因此,在需要频繁写操作的场景下,性能会非常低下

  3. 数据实时性差
    由于写操作会复制整个数组,因此在读取过程中,如果另一个线程完成了写操作,但由于读操作是在旧数组上进行的,读取线程依然会获取到写操作之前的数据(读操作可能读到旧数据,无法保证强一致性)

  4. 不适合频繁修改的场景
    大量写操作会导致内存和性能问题(如频繁更新的缓存)。


三、使用场景

1. 监听器列表(Listener Lists): 在事件监听器的管理中,当事件发生时需要遍历监听器,使用CopyOnWriteArrayList可以确保所有的监听器都能够被一致地访问。
// 典型应用:事件监听器管理
public class EventDispatcher {
    private final CopyOnWriteArrayList<EventListener> listeners = 
        new CopyOnWriteArrayList<>();

    public void addListener(EventListener listener) {
        listeners.add(listener);
    }

    public void fireEvent(Event event) {
        // 遍历时无需担心并发修改
        for (EventListener listener : listeners) {
            listener.onEvent(event);
        }
    }
}
  • 场景特点:监听器的注册(写)较少,事件触发(遍历读)频繁。

2. 配置管理存储一些系统的全局配置项,这些配置项很少变化,但需要频繁读取。例如,Web应用中需要频繁读取但很少修改的配置列表
// 全局配置信息存储
public class AppConfig {
    private static CopyOnWriteArrayList<String> configEntries = 
        new CopyOnWriteArrayList<>();

    public static void reloadConfig() {
        List<String> newConfig = loadConfigFromFile();
        configEntries.clear();
        configEntries.addAll(newConfig); // 全量替换配置
    }

    public static String getConfig(int index) {
        return configEntries.get(index);
    }
}
  • 场景特点:配置更新频率低,读取频率高。

3. 黑名单/白名单
  • 名单变动较少,但需要高并发查询。

3. 缓存 

        数据缓存中可能有频繁的读取,但更新相对较少,此时CopyOnWriteArrayList可以确保读取的高效性。

4. 日志记录 

        多线程环境中遍历需要输出到日志的数据时,使用CopyOnWriteArrayList可以避免并发修改带来的不一致性。

5.需要快照的场景

        在多线程环境中,应用程序可能需要对集合进行快照操作,CopyOnWriteArrayList由于其天然的快照机制,非常适合这种需求

四、与其他线程安全容器的对比

容器适用场景锁机制一致性
CopyOnWriteArrayList读多写少(如监听器列表)写时复制 + ReentrantLock弱一致性(快照)
Collections.synchronizedList读写均衡全表锁强一致性
ConcurrentLinkedQueue高并发队列(生产者-消费者)CAS 无锁弱一致性

五、最佳实践

  1. 避免频繁操作
    若需频繁修改,考虑使用 ConcurrentLinkedQueue 或 ConcurrentHashMap

  2. 控制数组大小
    存储大量数据时,频繁复制可能导致内存压力。

  3. 明确弱一致性的影响
    迭代器无法感知后续修改,需根据业务需求选择。


总结

CopyOnWriteArrayList 通过 写时复制 实现了读操作的无锁和高性能,但牺牲了写操作的效率和数据实时性。其核心价值在于 读多写少的高并发场景,开发者需根据业务特点权衡选择。

学海无涯,志当存远。燃心砺志,奋进不辍。愿诸君得此鸡汤,如沐春风,学业有成。若觉此言甚善,烦请赐赞一枚,共励学途,同铸辉煌

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值