文章目录

Java CopyOnWriteArrayList的写时复制机制与适用场景

发布于 2026-04-20 15:25:50 · 浏览 147 次 · 评论 0 条

Java CopyOnWriteArrayList的写时复制机制与适用场景

1. 认识CopyOnWriteArrayList

查看 Java集合框架中的CopyOnWriteArrayList类,你会发现它是ArrayList的一个线程安全变体。理解 CopyOnWriteArrayList的基本特性:它通过写时复制机制来保证线程安全,适用于读多写少的并发场景。

注意 CopyOnWriteArrayList位于java.util.concurrent包中,与ArrayList不同,它专门为多线程环境设计。

2. 写时复制机制原理

分析 写时复制(Copy-On-Write)的核心思想:当需要修改集合内容时,创建原始集合的一个副本,然后在副本上进行修改,最后将引用指向这个新的副本。

观察 CopyOnWriteArrayList的工作流程:

  1. 读取操作:直接访问底层数组,无需加锁,性能极高
  2. 写入操作:
    • 获取数组锁
    • 创建当前数组的新副本
    • 在副本上进行修改
    • 替换原数组引用为新数组
    • 释放数组锁

对比 传统同步集合与CopyOnWriteArrayList的锁机制差异:

  • 传统同步集合(如Collections.synchronizedList())通常对整个方法加锁
  • CopyOnWriteArrayList仅在修改时加锁,读取操作完全无锁

3. 源码解析

查看 CopyOnWriteArrayList的关键方法实现:

读取操作 - get(int index)方法:

public E get(int index) {
    return getArray()[index];
}

添加操作 - add(E e)方法:

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();
    }
}

修改操作 - set(int index, E element)方法:

public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = (E) elements[index];
        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

4. 性能分析

评估 CopyOnWriteArrayList在不同操作上的性能特点:

读取性能:

  • 读取操作无锁,时间复杂度为$O(1)$
  • 比较 ArrayList,性能几乎无差异

写入性能:

  • 添加元素时需要复制整个数组,时间复杂度为$O(n)$
  • 修改元素时同样需要复制数组,时间复杂度为$O(n)$
  • 删除元素时也需要复制数组,时间复杂度为$O(n)$

内存使用:

  • 注意 每次修改都会创建一个新数组,内存消耗较大
  • 计算 假设数组大小为$n$,每次修改会新增$n$个元素的内存占用

5. 适用场景

识别 CopyOnWriteArrayList最适合的场景:

  1. 读多写少的应用场景
  2. 遍历操作远多于修改操作的场景
  3. 不需要实时反映修改结果的场景
  4. 对迭代器一致性要求高的场景

举例 适合使用CopyOnWriteArrayList的场景:

  • 缓存实现,缓存数据变化不频繁但读取频繁
  • 监听器列表,注册和注销监听器操作较少,但通知监听器操作频繁
  • 白名单/黑名单管理,列表不经常变动但检查频繁

6. 不适用场景

警惕 不适合使用CopyOnWriteArrayList的场景:

  1. 写多读少的场景,性能会急剧下降
  2. 内存敏感的应用,频繁复制数组可能导致内存问题
  3. 需要实时反映修改结果的场景
  4. 集合规模较大的场景,复制开销巨大

举例 不适合使用CopyOnWriteArrayList的场景:

  • 频繁更新的日志收集系统
  • 实时数据处理系统
  • 大容量数据的临时存储

7. 实际应用示例

创建 一个简单的监听器模式实现:

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class ListenerExample {
    // 使用CopyOnWriteArrayList存储监听器
    private final List<EventListener> listeners = new CopyOnWriteArrayList<>();

    // 添加监听器
    public void addListener(EventListener listener) {
        listeners.add(listener);
    }

    // 移除监听器
    public void removeListener(EventListener listener) {
        listeners.remove(listener);
    }

    // 通知所有监听器
    public void notifyEvent(String eventData) {
        // 使用迭代器遍历,不会抛出ConcurrentModificationException
        for (EventListener listener : listeners) {
            listener.onEvent(eventData);
        }
    }

    // 监听器接口
    public interface EventListener {
        void onEvent(String eventData);
    }
}

使用 示例:

public class Main {
    public static void main(String[] args) {
        ListenerExample example = new ListenerExample();

        // 添加监听器
        example.addListener(event -> System.out.println("监听器1收到: " + event));
        example.addListener(event -> System.out.println("监听器2收到: " + event));

        // 触发事件
        example.notifyEvent("测试事件");

        // 再次添加监听器
        example.addListener(event -> System.out.println("监听器3收到: " + event));

        // 再次触发事件
        example.notifyEvent("第二个测试事件");
    }
}

8. 与其他并发集合的比较

集合类 线程安全机制 读取性能 写入性能 迭代器一致性 适用场景
CopyOnWriteArrayList 写时复制 极高 低 强一致性 读多写少
Vector 同步方法 低(同步开销) 低(同步开销) 弱一致性 一般并发场景
Collections.synchronizedList 同步方法 低(同步开销) 低(同步开销) 弱一致性 一般并发场景
ConcurrentHashMap 分段锁 高 较高 弱一致性 高并发读写
ConcurrentLinkedQueue CAS无锁 高 高 弱一致性 高并发队列

9. 最佳实践

遵循 以下最佳实践来使用CopyOnWriteArrayList:

  1. 限制 集合大小,避免频繁扩容导致复制开销
  2. 避免 在迭代过程中进行修改操作(虽然不会抛出异常,但迭代器不会看到新元素)
  3. 注意 内存使用,特别在长时间运行的应用中
  4. 考虑 使用new CopyOnWriteArrayList<>(initialCapacity)指定初始容量,减少扩容操作

举例 最佳实践代码:

// 指定初始容量减少扩容开销
List<String> list = new CopyOnWriteArrayList<>(1000);

// 批量添加元素减少复制次数
List<String> temp = new ArrayList<>();
for (int i = 0; i < 100; i++) {
    temp.add("Item" + i);
}
list.addAll(temp); // 只需复制一次

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文