手撕源码(五)CopyOnWriteArrayList(JDK8)

本文详细解读了Java并发包下的CopyOnWriteList类,介绍了其实现原理(读写分离、复制策略),并分析了关键方法如add(),get(),remove()和size()的源码,展示了如何在多线程环境下安全地使用这个线程安全的List类。
摘要由CSDN通过智能技术生成

一、使用示例

CopyOnWriteList:是 JDK 在并发包(java.util.concurrent)下的一个类,它是 ArrayList 的一个线程安全的变体。CopyOnWrite读写分离,读时共享,写时复制, 通俗的理解是:当我们往一个 List 添加元素的时候,不直接往当前 List 添加,而是先将当前 List 进行 Copy,复制出一个新的 List,然后新的容器里添加元素,添加完元素之后,再将原 List 的引用指向新的 List。这种设计使得读操作可以在原数组上进行,而不需要加锁,从而大大提高了读操作的并发性。

下面是一个经典的 10 线程并发场景下,CopyOnWriteArrayList 的线程安全使用实例:

import java.util.concurrent.*;

/**
 * <p> @Title DemoController
 * <p> @Description 测试Controller
 *
 * @author ACGkaka
 * @date 2023/4/24 18:02
 */
public class Test {

    public static void main(String[] args) throws InterruptedException {
        // 创建线程池
        ExecutorService pool = new ThreadPoolExecutor(
                10,
                10,
                1,
                TimeUnit.MINUTES,
                new LinkedBlockingQueue<>(10000),
                r -> new Thread(r, "MyThread-" + r.hashCode()),
                new ThreadPoolExecutor.AbortPolicy()
        );
        long start = System.currentTimeMillis();
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            int num = i;
            pool.execute(() -> {
                try {
                    Thread.sleep(100L);
                    list.add(num);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start) + " ms");
        System.out.println(list);
        pool.shutdown();
    }
}

二、源码解析

2.1 成员变量

CopyOnWriteArrayList 类的有两个成员变量:

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /**
    * The lock protecting all mutators
    * ------------------------------
    * 保护所有数组变更的锁。
    */
    final transient ReentrantLock lock = new ReentrantLock();

    /**
    * The array, accessed only via getArray/setArray.
    * ------------------------------
    * 该数组只能通过getArray/setArray进行访问。
    */
    private transient volatile Object[] array;
}
  • lock: 用于在每次数组变更的时候加锁。
  • array: 存储对象的数组。

2.2 new CopyOnWriteList<>() 解析

/**
 * Creates an empty list.
 * ------------------------------
 * 创建一个空的集合
 */
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}
/**
 * Sets the array.
 * ------------------------------
 * 设置数组
 */
final void setArray(Object[] a) {
    array = a;
}

可以看到在 new CopyOnWriteList 实例的时候,初始化了一个长度为0的空数组。

2.3 add() 解析

源码如下:

/**
 * Appends the specified element to the end of this list.
 * ------------------------------
 * 将指定的元素追加到此列表的末尾。
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#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);
        // 将数组最后一个元素赋值为e
        newElements[len] = e;
        // 更新数组
        setArray(newElements);
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}
/**
 * Gets the array.  Non-private so as to also be accessible
 * from CopyOnWriteArraySet class.
 * ------------------------------
 * 获取数组。非私有化,因此也可以从 CopyOnWriteArraySet 类访问。
 */
final Object[] getArray() {
    return array;
}

由源码可知,add() 操作是:通过使用实例内的锁,在往数组中添加元素的时候进行加锁操作,保证同一实例同一时间只有一处变更在进行

2.4 get() 解析

源码如下:

/**
 * 获取元素
 */
public E get(int index) {
    return get(getArray(), index);
}

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

由源码可知,get() 操作是:直接从数组中获取元素,不进行加锁操作

2.5 remove() 解析

源码如下:

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).  Returns the element that was removed from the list.
 * ------------------------------
 * 从此列表中的指定位置移除元素。将任何后续元素向左移动(从其索引中减去一)。
 * 返回从列表中移除的元素。
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
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();
    }
}

由源码可知,remove() 操作是:先获取指定元素,然后全局加锁,再分别将指定位置左侧和右侧的元素复制到新数组中M

2.6 size() 解析

/**
 * Returns the number of elements in this list.
 * ------------------------------
 * 返回列表中元素的数量。
 *
 * @return the number of elements in this list
 */
public int size() {
    return getArray().length;
}

由源码可知,size() 操作是:直接获取数组的长度,不进行加锁


三、总结

综上所述,当我们往一个 List 添加元素的时候,不直接往当前 List 添加,而是先 全局加锁,将当前 List 进行 Copy,复制出一个新的 List,然后新的容器里添加元素,添加完元素之后,再将原 List 的引用指向新的 List,删除等其他操作亦是如此。

整理完毕,完结撒花~ 🌻

  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
CopyOnWriteArrayList是一个线程安全的List实现,它通过每次修改操作(添加、删除、修改)时都创建一个新的底层数组来实现线程安全性。 CopyOnWriteArrayList的特点如下: 1. 线程安全:多个线程可以同时读取CopyOnWriteArrayList的内容,而不需要额外的同步机制。这使得它非常适合在读操作远远多于写操作的场景中使用。 2. 写操作的代价较高:每次对CopyOnWriteArrayList进行写操作时,都会创建一个新的底层数组,因此写操作的代价较高。 3. 实时性较低:由于写操作会创建新的底层数组,读取操作可能会看到旧的数据,因此CopyOnWriteArrayList的实时性较低。 使用CopyOnWriteArrayList的示例代码如下: ```java import java.util.concurrent.CopyOnWriteArrayList; public class Main { public static void main(String[] args) { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); list.add("Hello"); list.add("World"); for (String item : list) { System.out.println(item); } } } ``` 在上述代码中,我们创建了一个CopyOnWriteArrayList,并向其中添加了两个元素。然后使用增强for循环遍历CopyOnWriteArrayList中的元素,并打印输出。 需要注意的是,CopyOnWriteArrayList适用于读操作远远多于写操作的场景,如果写操作非常频繁,可能会导致性能问题。此外,CopyOnWriteArrayList不保证元素的顺序性,因为在写操作时会创建新的底层数组。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不愿放下技术的小赵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值