一句话说透Java里面的CopyWriteArrayList的内部实现

168 阅读2分钟

CopyOnWriteArrayList 内部实现:大白话解析

CopyOnWriteArrayList 就像  “多人协作时的文档快照” ,核心思想是  “写时复制” ,在修改数据时,先复制整个数组,修改副本,最后替换原数组。读操作无锁,写操作加锁,适合读多写少的场景(比如白板公告,大家常看但很少改)。


一、核心机制

  1. 读操作(如 get()

    • 直接读原数组,不加锁,无阻塞,高性能。
    • 即使其他线程在修改数据,读线程看到的仍是旧数组(类似读文档快照)。
  2. 写操作(如 add()set()remove()

    • 加锁ReentrantLock),确保同一时间只有一个线程修改。
    • 复制原数组,生成新数组(长度+1或-1)。
    • 修改新数组,完成操作。
    • 替换原数组引用,指向新数组。
    • 解锁,其他线程后续读取新数组。

示例

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

二、优缺点

优点缺点
读操作极快(无锁、无阻塞)写操作慢(复制数组开销大)
线程安全(读无需同步)内存占用高(频繁复制数组)
避免读写冲突(读写分离)数据弱一致性(读旧数据)

三、适用场景

  1. 高频读、低频写

    • 如系统配置、黑白名单(配置偶尔更新,大量查询)。
  2. 事件监听器列表

    • 监听器注册后频繁触发事件(读),但注册/注销(写)较少。
  3. 缓存数据快照

    • 缓存更新不频繁,但需要快速读取。

四、注意事项

  1. 迭代器弱一致性

    • 迭代器生成时持有旧数组的引用,遍历时看不到后续修改。

    CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(Arrays.asList(1, 2, 3));  
    Iterator<Integer> it = list.iterator();  
    list.add(4);  
    while (it.hasNext()) {  
        System.out.print(it.next() + " "); // 输出:1 2 3(看不到新增的4)  
    }  
    
  2. 避免频繁写操作

    • 若频繁修改(如每秒上千次),性能会急剧下降,建议改用 ConcurrentLinkedQueue 或 synchronizedList
  3. 内存敏感场景慎用

    • 大数组频繁复制可能导致内存压力或GC问题。

五、对比其他线程安全List

容器锁机制适用场景
synchronizedList全表锁简单同步,读写均衡
CopyOnWriteArrayList写时复制(读写分离)读多写少,高频读操作
Vector全表锁(过时)遗留代码兼容

六、总结

CopyOnWriteArrayList 是读多写少的利器

  • 读操作无锁:直接访问数组,性能极高。
  • 写操作安全:复制修改替换,隔离读写冲突。
  • 代价:写性能差,内存占用高,数据弱一致。

口诀
「写时复制真巧妙,读写分离性能高
读多写少最合适,内存开销要记牢
迭代器里旧数据,弱一致性别混淆!」