JDK 8 CopyOnWriteArrayList 详解及详细源码展示

JDK 8 CopyOnWriteArrayList 详解

Java 8 中的 CopyOnWriteArrayList 是一个线程安全的动态数组,它通过**写时复制(Copy-On-Write)**机制实现线程安全,特别适合读多写少的场景。下面我将详细解析其核心概念、实现原理和源码。

一、核心概念

1. 写时复制机制
  • 当执行写操作(如 add, remove)时,会先复制一份新数组,在新数组上修改,然后将原数组引用指向新数组。
  • 读操作(如 get, iterator)直接访问原数组,无需加锁。
2. 关键特性
  • 线程安全:写操作通过互斥锁保证原子性,读操作无锁。
  • 弱一致性:迭代器创建后不受后续修改影响,读取的是创建时的数组快照。
  • 内存开销:每次写操作都需要复制数组,内存占用高,不适合频繁写操作。

二、源码解析

1. 类定义与成员变量
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    // 可重入锁,保护写操作
    final transient ReentrantLock lock = new ReentrantLock();

    // 使用volatile保证数组可见性
    private transient volatile Object[] array;

    // 获取数组
    final Object[] getArray() {
        return array;
    }

    // 设置数组
    final void setArray(Object[] a) {
        array = a;
    }

    // 构造函数
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
}
2. add(E e) 方法
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(); // 释放锁
    }
}
3. remove(int index) 方法
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)
            // 如果删除的是最后一个元素,直接复制前len-1个元素
            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(); // 释放锁
    }
}
4. get(int index) 方法
public E get(int index) {
    return get(getArray(), index); // 直接读取数组,无需加锁
}

private E get(Object[] a, int index) {
    return (E) a[index];
}
5. 迭代器实现
public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

static final class COWIterator<E> implements Iterator<E> {
    // 迭代器创建时的数组快照
    private final Object[] snapshot;
    private int cursor;

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

    public boolean hasNext() {
        return cursor < snapshot.length;
    }

    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
    
    // 不支持删除操作
    public void remove() {
        throw new UnsupportedOperationException();
    }
}

三、关键特性分析

1. 读写分离
  • 读操作:无锁,直接访问数组,性能高。
  • 写操作:加锁并复制数组,开销大。
2. 弱一致性迭代器
  • 迭代器创建时保存数组快照,后续修改不会影响迭代结果。
  • 不支持 remove() 操作,调用会抛出 UnsupportedOperationException
3. 线程安全保障
  • 通过 ReentrantLock 保证写操作的原子性。
  • 使用 volatile 修饰数组保证可见性。

四、使用场景

  1. 读多写少的场景:如配置中心、缓存数据等。
  2. 需要弱一致性的场景:如网页访问统计、监控数据展示。
  3. 不支持并发修改的场景:迭代器不支持 remove() 操作。

五、性能对比

操作CopyOnWriteArrayListsynchronized List
读(get)无锁,性能极高需加锁,性能低
写(add)复制数组,开销大加锁,开销较小
迭代器创建O(1),创建快照O(1),直接引用
迭代器遍历无锁,性能极高需加锁,性能低

六、注意事项

  1. 内存占用:每次写操作都会创建新数组,可能导致内存占用过高。
  2. 写操作开销:写操作涉及数组复制,频繁写会导致性能下降。
  3. 弱一致性:迭代器不反映最新数据,适用于对实时性要求不高的场景。

七、总结

CopyOnWriteArrayList 通过写时复制机制实现了线程安全的动态数组,在读多写少的场景下表现优异。其核心思想是用空间换时间,通过无锁读和数组复制保证并发性能。但由于每次写操作都需要复制数组,内存开销较大,不适合频繁写操作的场景。理解其设计原理和适用场景,能帮助开发者在不同场景下做出合理选择。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值