CopyOnWriteArrayList:读多写少场景下的并发利器

读写分离的妙用

想象你在图书馆查阅资料:

  • 传统做法:有人借书时整个图书馆闭馆(类似Vector全表锁)
  • 现代做法:读者可以继续阅读书架上的书,管理员在后台准备新版书籍(CopyOnWrite机制)

这就是CopyOnWriteArrayList的核心思想!今天我们就来深入探讨这个"读写分离"的并发列表实现。


一、基础认知:COW是什么?

1. 核心特点演示

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Java"); // 写操作
list.add("Python");

// 读操作可以并发执行
list.forEach(System.out::println); // 安全遍历

2. 关键特性速览

特性说明
写时复制修改操作时复制整个数组
读操作无锁所有读取操作都不需要同步
弱一致性迭代器反映的是创建时的数组快照
适用场景读多写少(如监听器列表、配置信息等)

二、底层原理:写时复制的魔法

1. 数据结构图解

在这里插入图片描述
在这里插入图片描述

  • volatile数组引用:保证内存可见性
  • ReentrantLock:写操作互斥

2. 写操作流程

线程A 数组 新数组 数组引用 获取锁 复制新数组(长度+1) 执行修改操作 原子替换引用 释放锁 线程A 数组 新数组 数组引用

三、四大典型应用场景

场景1:事件监听器列表

// 观察者模式的安全实现
class EventSource {
    private CopyOnWriteArrayList<Listener> listeners 
        = new CopyOnWriteArrayList<>();
    
    void addListener(Listener l) { listeners.add(l); }
    
    void fireEvent(Event e) {
        for (Listener l : listeners) { // 安全遍历
            l.onEvent(e);
        }
    }
}

场景2:配置信息管理

// 全局配置的热更新
CopyOnWriteArrayList<Config> configs = new CopyOnWriteArrayList<>();

// 后台线程定期更新
void reloadConfigs() {
    Config[] newConfigs = loadConfigsFromDB();
    configs = new CopyOnWriteArrayList<>(newConfigs);
}

// 业务线程安全读取
String getConfig(String key) {
    for (Config c : configs) { /*...*/ }
}

场景3:黑白名单过滤

// 动态更新的IP白名单
CopyOnWriteArrayList<String> whiteList = new CopyOnWriteArrayList<>();

boolean allowAccess(String ip) {
    return whiteList.contains(ip); // 安全读取
}

void updateList(List<String> newIps) {
    whiteList = new CopyOnWriteArrayList<>(newIps); // 原子替换
}

场景4:日志处理系统

// 多线程日志收集
CopyOnWriteArrayList<String> logBuffer = new CopyOnWriteArrayList<>();

// 日志写入线程
void log(String message) {
    logBuffer.add(message);
}

// 日志处理线程(定期批量处理)
void processLogs() {
    for (String log : logBuffer) {
        // 处理日志...
    }
    logBuffer.clear();
}

四、性能特点与注意事项

1. 性能对比测试(10万次操作)

操作ArrayListVectorCopyOnWriteArrayList
读(get)5ms120ms8ms
写(add)7ms150ms250ms
迭代6ms130ms10ms

2. 使用注意事项

// 错误用法:频繁写入
CopyOnWriteArrayList<Integer> data = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100000; i++) { // 导致大量数组拷贝!
    data.add(i);
}

// 正确用法:批量添加
data.addAll(Arrays.asList(numbers)); // 单次复制

五、源码级揭秘:关键实现细节

1. 写操作实现(JDK17源码片段)

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. 迭代器实现

static final class COWIterator<E> implements ListIterator<E> {
    private final Object[] snapshot; // 创建时的数组快照
    private int cursor;
    
    public E next() {
        if (!hasNext()) throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
}

六、与相关容器的对比

特性ArrayListVectorCopyOnWriteArrayList
线程安全✅(全表锁)✅(写时复制)
读性能⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
写性能⭐⭐⭐⭐⭐⭐⭐
迭代安全✅(同步)✅(快照)
内存占用高(写时复制副本)

七、最佳实践指南

1. 选型决策树

极少
一般
频繁
需要线程安全?
使用ArrayList
写操作频率?
CopyOnWriteArrayList
Collections.synchronizedList
ConcurrentLinkedQueue等

2. 优化建议

  • 预分配空间:在已知大小时通过构造函数指定
  • 批量操作:优先使用addAll而不是循环add
  • 避免size():遍历时直接使用迭代器

3. 替代方案

当写操作较多时考虑:

// 并发队列方案
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();

// 并发Map方案
ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();

结语:合适的就是最好的

CopyOnWriteArrayList就像:

  • 📚 图书馆的影印服务:不影响读者阅读原版
  • 🕰️ 时间机器:迭代器看到的是过去的快照
  • 🛡️ 读写盾牌:完美隔离读写操作

记住它的黄金使用场景:读多写少数据量不大的情况。用对了场景,它就是你并发编程中的神兵利器!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农技术栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值