CopyOnWriteArrayList 简介

CopyOnWriteArrayList 是 Java 并发包 java.util.concurrent 中提供的一个线程安全的 List 实现类。它通过 写时复制(Copy-on-Write) 机制来保证线程安全,适用于 读多写少 的并发场景。


🧠 一、什么是 CopyOnWriteArrayList?

核心思想:

  1. 读操作:直接访问当前数组,无锁且线程安全
  2. 写操作:复制底层数组 → 修改新数组 → 替换旧数组(加锁保证原子性
  3. 迭代器:基于创建时的数组快照,不反映后续修改

这种策略牺牲了写性能,但极大提升了读性能和并发安全性。


📚 二、源码

1. 核心数据结构

public class CopyOnWriteArrayList<E> {

    // 使用 ReentrantLock 来控制写操作的同步
    final transient ReentrantLock lock = new ReentrantLock();

    // 底层使用 volatile 数组来保存元素
    private volatile transient Object[] array;
}
  • array 是 volatile 的,确保线程间的可见性。
  • 所有写操作都必须获取 lock 锁。
  • 读操作直接访问 array,不加锁。

2. 构造方法

public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    if (c.getClass() == CopyOnWriteArrayList.class)
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
        elements = c.toArray();
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    setArray(elements);
}

3. 添加元素:add 方法

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();  // 加锁
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // 创建一个新的数组,长度+1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 在末尾插入新元素
        newElements[len] = e;
        // 替换底层数组
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

说明:

  • 写操作需要加锁;
  • 每次写操作都会复制整个数组;
  • 新数组替换旧数组使用 setArray(),它是原子性的。

4. 获取元素:get 方法

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

private E get(Object[] a, int index) {
    return (E) a[index];
}

特点:

  • 不加锁;
  • 直接访问当前数组;
  • 读取的是快照数据,可能不是最新的。

5. 设置元素:set 方法

public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);

        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

6. 删除元素:remove 方法

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index, numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}

🎯 三、注意事项

  1. ❌ 避免频繁修改(尤其是大数据量)
  2. ❌ 不要用于强一致性场景(如金融交易)
  3. ✅ 优先使用 Iterator 而非 for-each(避免隐蔽的 ConcurrentModificationException

🔍 四、适用场景

  • 读多写少的配置数据:如缓存黑白名单
  • 元素数量不大
  • 实时性要求低的集合:如历史日志记录
  • 监听器/观察者模式:注册监听器少,事件触发频繁

🧪 五、示例

并发读写测试

import java.util.concurrent.CopyOnWriteArrayList;

public class COWTest {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        // 添加元素
        list.add("A");
        list.add("B");
        list.add("C");

        // 多线程并发读
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                for (String s : list) {
                    System.out.println(Thread.currentThread().getName() + ": " + s);
                }
            }).start();
        }

        // 写线程
        new Thread(() -> {
            list.add("D");
            System.out.println("Added D");
        }).start();
    }
}

说明:

  • 所有读线程输出的可能是添加前或添加后的列表;
  • 但不会出现并发异常或脏数据。

读多写少的监听器管理

import java.util.concurrent.CopyOnWriteArrayList;

class EventDispatcher {
    // 线程安全的监听器列表
    private final CopyOnWriteArrayList<EventListener> listeners = 
        new CopyOnWriteArrayList<>();

    // 注册监听器(写操作较少)
    public void addListener(EventListener listener) {
        listeners.add(listener);
    }

    // 触发事件(读操作频繁且无锁)
    public void fireEvent(String event) {
        for (EventListener listener : listeners) { // 基于快照的迭代器
            listener.onEvent(event); // 无锁遍历,线程安全
        }
    }
}

@FunctionalInterface
interface EventListener {
    void onEvent(String event);
}

📈 六、对比

操作ArrayListCollections.synchronizedListCopyOnWriteArrayList
读(get)无锁有锁竞争无锁
写(add)非安全全局锁复制数组
迭代器安全性快速失败需手动同步弱一致

💡 结论:在 读操作 >> 写操作(≥ 100:1)的场景中选用


✅ 七、总结

特性原理
线程安全的读读操作直接访问 volatile 数组,无锁
写操作互斥通过 synchronized 加锁,保证同时只有一个线程写
弱一致性迭代器迭代器基于历史快照,不反映后续修改
内存开销大每次写操作复制整个数组,适合 读多写少 场景
数据实时性弱读操作可能读到旧数据,不适合强一致性场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值