Java CopyOnWriteArrayList Copy on Write(写时复制)

本文深入剖析了Java中CopyOnWriteArrayList的工作原理,重点介绍了其如何利用写时复制机制来实现线程安全的集合操作,同时探讨了该机制带来的数据一致性问题。

Java CopyOnWriteArrayList Copy on Write(写时复制)分析

Copy on Write这种机制用在集合上,当读取元素,先复制原有集合内容,在集合的某个副本上进行遍历操作。当发生写操作时,首先复制原有集合内容生成一个副本,随后在这个副本上进行写操作。

        CopyOnWriteArrayList<Integer> ids = new CopyOnWriteArrayList<>();
        ids.add(5);
        for (Integer i : ids) {
            System.out.println(i);
            ids.add(6);
        }

        for (Integer i : ids) {
            System.out.println(i);
        }

以上代码以CopyOnWriteArrayList为例。我们知道使用foreach语法糖进行集合遍历时,实际上是通过迭代器对象来进行遍历。

CopyOnWriteArrayList内部维护了array对象,并COWIterator进行元素迭代。访问通array对象内的元素。

    private transient volatile Object[] array;

当循环开始首先创建COWIterator对象,这时COWIterator内部维护的snapshot对象指向CopyOnWriteArrayList的array对象。而在循环执行过程中snapshot将不会发生变化。因此即使在循环中对CopyOnWriteArrayList进行修改,也不会影响snapshot结果。所以不会造成ConcurrentModificationException的问题。

        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }

当对list执行修改操作,这是会新创建一段存储空间,并拷贝原有数据。随后在这个新的副本空间上进行修改。当修改完毕执行setArray,修改
CopyOnWriteArrayList中array成员,array指向新的存储空间。而迭代器在初始化时保存的是旧的存储空间引用。因此写操作不会对当前循环发生影响。当再次循环遍历时可以看到新增加的元素输出。

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

在这里插入图片描述

此外我们看到add操作是加了锁。当多线程操作同时发生修改操作,若不加锁会导致数据发生覆盖,添加数据丢失。

总结

Copy on write提供了一种机制,当遍历一个集合时,首先纪录集合当时的快照引用。那么遍历就是对集合某个时刻快照进行遍历。当发生写操作会生成新的副本,修改操作发生在新的副本上,而不对原有存储空间修改。这种思路下,读写处理了不同的数据存储区域, 避免了并发修改异常的问题。但这种方式也会造成数据读取时短暂不一致状态。

参考

jdk1.8 java.util.concurrent.CopyOnWriteArrayList

`CopyOnWriteArrayList` 是 Java 并发包 `java.util.concurrent` 中提供的一个线程安全的 `List` 实现。它采用了一种非常高效的并发控制机制:**复制Copy-On-Write,简称 COW)**。这种机制特别适合**读多少**的并发场景。 --- ## ✅ 复制Copy-On-Write)机制详解 ### 📌 原理概述 复制的核心思想是: > **在操作(如添加、修改、删除),先复制一份当前数组的副本,在副本上进行修改,完成后将原数组引用指向的副本。读操作始终在原数组上进行,不需要加锁。** 这样做的好处是: - **读操作完全无锁,性能极高** - **操作是线程安全的(通过 ReentrantLock)** - **读之间不阻塞,适合高并发场景** --- ## ✅ 示例代码演示 ```java import java.util.concurrent.CopyOnWriteArrayList; public class CopyOnWriteExample { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); // 添加元素 list.add("Java"); list.add("Python"); // 读线程 Runnable readTask = () -> { for (String s : list) { System.out.println(Thread.currentThread().getName() + " 读取: " + s); } }; // 线程 Runnable writeTask = () -> { list.add("Go"); System.out.println(Thread.currentThread().getName() + " 入: Go"); }; // 启动多个读线程 for (int i = 0; i < 3; i++) { new Thread(readTask, "读线程-" + i).start(); new Thread(writeTask, "线程-" + i).start(); } } } ``` --- ## ✅ 内部机制详解 我们来看 `CopyOnWriteArrayList` 的关键方法实现(伪代码说明): ### 1. 添加元素(`add(E e)`) ```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); // 替换为数组 return true; } finally { lock.unlock(); } } ``` ### 2. 读取元素(`get(int index)`) ```java public E get(int index) { return get(getArray(), index); // 直接使用当前数组,无锁 } ``` ### 3. 迭代器(`iterator()`) ```java Iterator<String> it = list.iterator(); while (it.hasNext()) { System.out.println(it.next()); } ``` > 迭代器始终遍历的是**创建的数组快照**,因此不会抛出 `ConcurrentModificationException`,也不会被操作影响。 --- ## ✅ 优缺点分析 | 特性 | 说明 | |------|------| | ✅ **读操作无锁** | 读取不需要加锁,性能高 | | ✅ **迭代安全** | 迭代基于快照,不会抛出并发修改异常 | | ❌ **操作开销大** | 每次操作都要复制数组,性能低 | | ❌ **内存占用高** | 多个数组副本存在,占用内存多 | | ❌ **最终一致性** | 读可能读到数据,不是强一致性 | --- ## ✅ 适用场景 `CopyOnWriteArrayList` 最适合以下场景: - **读操作远远多于操作** - **数据变化不频繁** - **需要线程安全且不想引入锁机制** - **允许读取数据(最终一致性)** 典型应用场景包括: - 缓存 - 事件监听器列表 - 配置管理 - 白名单/黑名单维护 --- ## ✅ 总结 `CopyOnWriteArrayList` 的复制机制通过牺牲性能来换取极致的读性能,是 Java 中处理**读多少**并发场景的利器。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值