CopyOnWriteArrayList 详解

CopyOnWriteArrayList 是 Java 中一种线程安全的 List 实现,它通过一种独特的写时复制机制(Copy-On-Write)来保证线程安全性。该类位于 java.util.concurrent 包中,适用于读操作远多于写操作的场景。在这种场景下,CopyOnWriteArrayList 可以提供比其他线程安全的集合(如 VectorCollections.synchronizedList)更高的并发读性能。

1. CopyOnWriteArrayList 概述

CopyOnWriteArrayListList 接口的实现类,其底层使用一个 volatile 修饰的数组来存储元素。它的关键特性是:当对列表进行写操作(如添加、删除或修改元素)时,会创建一个底层数组的新副本,修改操作将在这个副本上进行,修改完成后,再将该副本设置为新的底层数组。这种“写时复制”的机制确保了读操作的线程安全性,因为读操作总是访问稳定的、不会被修改的数组。

1.1 线程安全性

CopyOnWriteArrayList 的线程安全性来自于以下几点:

  • 写操作的独立性:每次写操作(如 add()remove()set() 等)都会创建一个底层数组的新副本,因此不会影响当前正在读取的线程。这种方式避免了使用锁机制的复杂性,也避免了锁竞争。

  • 读操作的无锁化:读操作(如 get()iterator() 等)不需要加锁,因为底层数组在读操作期间是不可变的。由于读取操作总是访问原始数组的快照,因此不需要同步机制。

1.2 性能特点

CopyOnWriteArrayList 的性能特点如下:

  • 读操作高效:由于读操作不需要加锁,因此在高并发的环境下,CopyOnWriteArrayList 可以提供非常高效的读性能。

  • 写操作开销较大:每次写操作都会创建数组的副本,这在涉及大量写操作的场景下会显著增加内存开销和垃圾回收压力。因此,它适用于读多写少的场景。

2. CopyOnWriteArrayList 的内部实现

为了更深入地理解 CopyOnWriteArrayList,需要了解它的内部实现机制。下面是它的关键实现细节。

2.1 底层数据结构

CopyOnWriteArrayList 的底层数据结构是一个被 volatile 修饰的数组 Object[]

private transient volatile Object[] array;

通过 volatile 关键字修饰,确保了 array 的修改对所有线程都是可见的。

2.2 读操作实现

读操作的核心在于操作的是一个不可变的数组副本,这使得读操作非常快速和高效。典型的读操作如 get(int index),实现如下:

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

getArray() 方法简单地返回当前的 array,由于 arrayvolatile 修饰的,因此不会出现线程不可见的问题,且读操作不需要进行任何同步处理。

2.3 写操作实现

写操作则相对复杂一些,因为它涉及创建数组副本。以下是 add(E e) 方法的实现:

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

可以看到,写操作首先获取了一个 ReentrantLock 锁来确保操作的原子性。然后:

  1. 调用 getArray() 获取当前的数组。
  2. 使用 Arrays.copyOf() 方法创建数组的新副本,并将新元素添加到副本中。
  3. 调用 setArray() 将新的数组副本设置为当前数组。
  4. 解锁并返回。

2.4 迭代器(Iterator)

CopyOnWriteArrayList 的迭代器实现与普通的 ArrayList 不同,它返回的是一个对底层数组的快照(snapshot),这意味着迭代器创建之后的任何修改操作(如 add()remove() 等)都不会影响迭代器。以下是 CopyOnWriteArrayList 迭代器的实现方式:

public Iterator<E> iterator() {
    return new COWIterator<>(getArray(), 0);
}

COWIterator 是一个内部类,它的实现方式确保了迭代器是弱一致性(weakly consistent)的。迭代器的 next() 方法总是读取快照中的元素,因此不会抛出 ConcurrentModificationException 异常。

3. CopyOnWriteArrayList 的应用场景

CopyOnWriteArrayList 非常适合以下场景:

  • 读操作远多于写操作:如果一个集合的读操作非常频繁,而写操作相对较少,那么 CopyOnWriteArrayList 是一个很好的选择。

  • 迭代过程中不希望受到修改的影响:如果需要在迭代过程中避免 ConcurrentModificationException 异常的出现,CopyOnWriteArrayList 是一个很好的选择,因为它的迭代器是基于快照的。

  • 需要线程安全的 List:如果需要线程安全的 List 实现,并且写操作不会特别频繁,可以选择 CopyOnWriteArrayList

示例应用场景

  1. 缓存数据的场景:假设一个应用程序需要缓存一些数据,这些数据需要频繁读取,但偶尔会更新。使用 CopyOnWriteArrayList 可以在不加锁的情况下高效读取缓存数据,同时支持缓存的安全更新。

  2. 发布订阅模式:在实现发布订阅模式时,订阅者列表通常会被频繁遍历(读取),而订阅者的添加和移除相对较少,CopyOnWriteArrayList 是一个理想的选择。

4. CopyOnWriteArrayList 的优缺点

4.1 优点

  • 线程安全:由于读操作无锁,写操作采用独立副本,CopyOnWriteArrayList 是线程安全的。
  • 高效的读操作:在多线程环境下,读操作不需要加锁,非常高效。
  • 避免了 ConcurrentModificationException:由于迭代器是基于底层数组的快照,迭代过程中不会出现并发修改异常。

4.2 缺点

  • 内存开销大:每次写操作都会创建数组副本,如果列表很大,频繁写操作会导致大量的内存消耗。
  • 不适合写多读少的场景:由于写操作需要复制数组,写操作的开销较大,在写操作频繁的场景下性能较差。

5. 与其他线程安全集合的对比

  • Vector 相比Vector 通过同步方法保证线程安全,但每个操作都需要获取锁,会导致较高的锁争用。而 CopyOnWriteArrayList 在读操作上不加锁,因此在读多写少的场景下性能更优。

  • Collections.synchronizedList() 相比Collections.synchronizedList() 也是通过同步方法实现线程安全,锁粒度大,同步开销高,CopyOnWriteArrayList 在高并发读操作下表现更好。

6. 总结

CopyOnWriteArrayList 是一种适用于读多写少场景的线程安全集合,通过写时复制机制提供了高效的读性能和线程安全性。它的设计理念和实现方式使其非常适合在需要高效读操作的多线程环境中使用。然而,由于写操作的内存开销和性能问题,在选择使用 CopyOnWriteArrayList 时,应根据具体的使用场景权衡其优缺点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Flying_Fish_Xuan

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

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

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

打赏作者

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

抵扣说明:

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

余额充值