Java并发集合 - CopyOnWriteArrayList详解

1. 什么是CopyOnWriteArrayList?

CopyOnWriteArrayList是java.util.concurrent包中提供的一个线程安全的ArrayList。它通过一种称为“写时复制”(Copy-On-Write)的方法来实现线程安全。简而言之,每当我们尝试修改这个列表(如添加、删除元素)时,它实际上并不直接在当前的列表上进行修改,而是先将当前列表复制一份,然后在这个副本上进行修改,修改完毕后再将原列表的引用指向新修改过的列表。这种机制确保了在读操作期间数据的不变性,非常适合读多写少的场景。

2. CopyOnWriteArrayList源码解析

2.1 类继承关系

  • 实现List接口:List接口定义了对列表的基本操作。
  • 实现RandomAccess接口:表示可以随机访问,数组具有随机访问的属性。
  • 实现Cloneable接口:表示可克隆。
  • 实现了Serializable接口:表示可序列化。
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {}

2.2 类属性

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // 版本序列号
    private static final long serialVersionUID = 8673264195747942595L;
    // 可重入锁定
    final transient ReentrantLock lock = new ReentrantLock();
    //  对象数组,用于存放元素 CopyOnWriteArrayList 核心,使用volatile保证array的可见性和有序性。
    private transient volatile Object[] array;
    // Unsafe 类
    private static final sun.misc.Unsafe UNSAFE;
    // lock域的内存偏移量
    private static final long lockOffset;
     static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = CopyOnWriteArrayList.class;
            lockOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("lock"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

2.3 核心函数

// 向列表末尾添加一个元素
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();
    }
}

// 此函数是移除列表上的元素-返回旧值
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;
        // 需要移动的个数为0
        if (numMoved == 0)
            // 直接复制一个
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            // 新生数组
            Object[] newElements = new Object[len - 1];
            // 复制index之前的数据
            System.arraycopy(elements, 0, newElements, 0, index);
            // 复制index之后的数据
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            // 设置数组
            setArray(newElements);
        }
        // 返回旧值
        return oldValue;
    } finally {
        lock.unlock();
    }
}
// 此函数用于用指定的元素替代此列表指定位置上的元素,也是基于数组的复制来实现的。
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 {
            // Not quite a no-op; ensures volatile write semantics
            // 设置数组
            setArray(elements);
        }
        // 返回旧值
        return oldValue;
    } finally {
        lock.unlock();
    }
}

2.4 类构造器

// 默认构造器
public CopyOnWriteArrayList() {
    // 设置数组
    setArray(new Object[0]);
}

public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    if (c.getClass() == CopyOnWriteArrayList.class) // 类型相同
        // 获取c集合的数组
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else { // 类型不相同
        // 将c集合转化为数组并赋值给elements
        elements = c.toArray();
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elements.getClass() != Object[].class) // elements类型不为Object[]类型
            // 将elements数组转化为Object[]类型的数组
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    // 设置数组
    setArray(elements);
}

2.5 内部类

CopyOnWriteArrayList 的内部类 COWIterator 是一个实现了 ListIterator 接口的迭代器类,专门为 CopyOnWriteArrayList 定制。它提供了一种安全的方式来遍历 CopyOnWriteArrayList,即使在多线程环境中也不会抛出 ConcurrentModificationException。我们来逐个分析这个内部类的主要部分。

static final class COWIterator<E> implements ListIterator<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 boolean hasPrevious() {
        return cursor > 0;
    }
	// 用于获取下一个元素,并更新cursor的位置
    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }
	// 用于获取下一个元素,并更新cursor的位置
    @SuppressWarnings("unchecked")
    public E previous() {
        if (! hasPrevious())
            throw new NoSuchElementException();
        return (E) snapshot[--cursor];
    }
    // 返回下一个元素索引
    public int nextIndex() {
        return cursor;
    }
    // 返回上一个元素索引
    public int previousIndex() {
        return cursor-1;
    }

    // 不支持操作
    public void remove() {
        throw new UnsupportedOperationException();
    }

    // 不支持操作
    public void set(E e) {
        throw new UnsupportedOperationException();
    }

    // 不支持操作
    public void add(E e) {
        throw new UnsupportedOperationException();
    }

    // 允许使用 lambda 表达式或方法引用来迭代剩余的元素
    @Override
    public void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        Object[] elements = snapshot;
        final int size = elements.length;
        for (int i = cursor; i < size; i++) {
            @SuppressWarnings("unchecked") E e = (E) elements[i];
            action.accept(e);
        }
        cursor = size;
    }
}

3. CopyOnWriteArrayList的使用示例

public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();
        // 添加元素
        cowList.add("Java");
        cowList.add("Python");
        cowList.add("C++");

        // 创建线程来修改列表
        Thread threadOne = new Thread(() -> {
            cowList.add("JavaScript");
            cowList.remove("Python");
        });
        
        // 启动线程
        threadOne.start();

        // 同时进行迭代操作
        Iterator<String> iterator = cowList.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
            try {
                Thread.sleep(100); // 延迟以模拟读取操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("=============更新之后=============");
        cowList.forEach(System.out::println);
    }
}

在上述示例中,我们创建了一个CopyOnWriteArrayList并在一个线程中对其进行了修改。同时,主线程中遍历了列表。由于CopyOnWriteArrayList的特性,即使列表在遍历过程中被修改,迭代器也不会抛出ConcurrentModificationException异常,因为迭代器是在列表被修改之前创建的。

4. CopyOnWriteArrayList的使用场景

  1. 读多写少的应用场景:如果应用程序中读取操作的频率远高于写入操作,CopyOnWriteArrayList将非常适用。
  2. 迭代器的一致性视图:当需要确保在迭代过程中集合不会被并发修改时,CopyOnWriteArrayList提供了一种简单有效的解决方案。
  3. 避免锁的开销:在某些场景下,使用传统的同步集合(如VectorCollections.synchronizedList)可能会因为频繁的锁操作而导致性能问题。CopyOnWriteArrayList通过减少锁的使用提供了一种高效的替代方案。
  • 13
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

--土拨鼠--

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

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

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

打赏作者

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

抵扣说明:

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

余额充值