一文带你吃透CopyWriteArrayList的内部实现

CopyOnWriteArrayList 是 Java 并发包中提供的线程安全列表,采用 写时复制(Copy-On-Write) 策略实现高并发读取,适用于 读多写少 的场景。本文将从数据结构、核心操作、优缺点等方面进行详细解析其实现机制。

一、数据结构

  1. 底层存储

    • 使用 volatile 修饰的数组保存元素,确保多线程可见性:

      java 复制代码
      private transient volatile Object[] array;
    • 所有操作均基于此数组的快照,写操作会复制并替换原数组。

  2. 锁机制

    • 通过 ReentrantLock 保证写操作的原子性:

      java 复制代码
      final transient ReentrantLock lock = new ReentrantLock();

二、核心操作

1. 写操作(Add/Set/Remove)

  • 步骤

    1. 加锁 :获取 ReentrantLock
    2. 复制数组:创建原数组的副本。
    3. 修改副本:在副本上执行写操作(如添加、删除元素)。
    4. 替换原数组 :将 volatile 数组引用指向新副本。
    5. 释放锁:解锁,允许其他写操作进行。
  • 示例(add 方法)

    java 复制代码
    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); // volatile 写,保证可见性
            return true;
        } finally {
            lock.unlock();
        }
    }

2. 读操作(Get/Iterate)

  • 无锁访问:直接读取当前数组,无需同步。

  • 示例(get 方法)

    java 复制代码
    public E get(int index) {
        return get(getArray(), index); // 直接访问 volatile 数组
    }

3. 迭代器

  • 弱一致性:迭代器基于创建时的数组快照,不会反映后续修改。

  • 实现代码

    java 复制代码
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
    • COWIterator 内部保存快照数组,遍历期间不受写操作影响。

三、性能与适用场景

场景 性能表现 示例应用
高频读取 极高(无锁,直接访问数组) 事件监听器列表、配置信息缓存
低频写入 较低(复制数组开销大) 动态添加少量监听器或配置项
迭代遍历 安全但滞后(基于旧快照) 日志记录、批量读取操作

四、优缺点分析

优点

  • 线程安全:写操作通过锁和复制保证原子性,读操作无锁。
  • 无并发修改异常 :迭代器遍历旧快照,不会抛出 ConcurrentModificationException
  • 高读性能:读操作无锁,适合读多写少场景。

缺点

  • 写性能差 :每次写操作需复制整个数组,时间复杂度为 O(n)
  • 内存占用高:频繁写操作导致内存碎片和临时对象增加。
  • 数据延迟:迭代器和读操作可能访问旧数据,无法保证强一致性。

五、对比其他线程安全列表

实现类 锁机制 读性能 写性能 一致性
CopyOnWriteArrayList 写时复制 + 锁 极高 弱一致性(快照)
Vector 全表锁 中等 强一致性
Collections.synchronizedList 全表锁 中等 强一致性

六、使用建议

  1. 适用场景

    • 监听器管理(如 GUI 事件监听)。
    • 读多写少的配置或缓存数据。
    • 需要避免迭代时并发修改异常的场合。
  2. 避坑指南

    • 避免频繁写操作 :如实时数据更新,应选择 ConcurrentLinkedQueueConcurrentHashMap
    • 监控内存使用:大数组频繁复制可能导致 GC 压力。
    • 慎用批量写入:尽量合并多次写操作,减少复制次数。

七、源码关键点

  1. 数组替换

    java 复制代码
    final void setArray(Object[] a) {
        array = a; // volatile 写,保证线程可见性
    }
  2. 锁的使用

    • 所有修改操作(addsetremove)均通过 ReentrantLock 同步。

通过写时复制策略,CopyOnWriteArrayList 在特定场景下实现了高效的线程安全读取,是 Java 并发编程中处理读多写少问题的经典设计。开发者需根据实际需求权衡其优缺点,合理选择数据结构。

更多分享

  1. 一文带你吃透Android中常见的高效数据结构
  2. 详解:ArrayMap和SparseArray在HashMap上面的改进
  3. 详解:HashMap与TreeMap、HashTable的区别
  4. 详解:Set集合是如何保证元素不重复的
  5. 详解:LinkedHashMap的工作原理和实现
  6. 一文带你搞懂HashSet和TreeSet的区别
相关推荐
bytebeats几秒前
Jetpack Compose 1.9: 核心新特性简介
android·android jetpack
Icey_World4 分钟前
Mysql笔记-错误条件\处理程序
android
AAA修煤气灶刘哥15 分钟前
Java+AI 驱动的体检报告智能解析:从 PDF 提取到数据落地全指南
java·人工智能·后端
wxy31925 分钟前
嵌入式LINUX——————TCP并发服务器
java·linux·网络
★YUI★1 小时前
学习游戏制作记录(玩家掉落系统,删除物品功能和独特物品)8.17
java·学习·游戏·unity·c#
微小的xx1 小时前
java + html 图片点击文字验证码
java·python·html
大王派来巡山的小旋风1 小时前
Kotlin基本用法之集合(一)
android·程序员·kotlin
用户2018792831671 小时前
智能广播系统(RemoteCallbackList)的诞生
android
mask哥1 小时前
详解flink java基础(一)
java·大数据·微服务·flink·实时计算·领域驱动
克拉克盖博1 小时前
chapter03_Bean的实例化与策略模式
java·spring·策略模式