一文带你吃透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的区别
相关推荐
pianmian12 小时前
类(JavaBean类)和对象
java
我叫小白菜3 小时前
【Java_EE】单例模式、阻塞队列、线程池、定时器
java·开发语言
Albert Edison3 小时前
【最新版】IntelliJ IDEA 2025 创建 SpringBoot 项目
java·spring boot·intellij-idea
超级小忍4 小时前
JVM 中的垃圾回收算法及垃圾回收器详解
java·jvm
weixin_446122464 小时前
JAVA内存区域划分
java·开发语言·redis
勤奋的小王同学~4 小时前
(javaEE初阶)计算机是如何组成的:CPU基本工作流程 CPU介绍 CPU执行指令的流程 寄存器 程序 进程 进程控制块 线程 线程的执行
java·java-ee
TT哇4 小时前
JavaEE==网站开发
java·redis·java-ee
2401_826097624 小时前
JavaEE-Linux环境部署
java·linux·java-ee
缘来是庄5 小时前
设计模式之访问者模式
java·设计模式·访问者模式
雨白5 小时前
Jetpack系列(三):Room数据库——从增删改查到数据库平滑升级
android·android jetpack