• CopyOnWriteArrayList源码分析


    1.CopyOnWriteArrayList

    Copy-On-Write简称COW,是一种用于集合的并发访问的优化策略。基本思想是:当我们往一个集合容器中写入元素时(添加、修改、删除),并不会直接在集合容器中写入,而是先将当前集合容器进行Copy,复制出一个新的容器,然后新的容器里写入元素,写入操作完成之后,再将原容器的引用指向新的容器

    这样做的好处:实现对CopyOnWrite集合容器写入操作时的线程安全,但同时并不影响进行并发的读取操作。所以CopyOnWrite容器也是一种读写分离的思想。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发集合容器,它们是CopyOnWriteArrayListCopyOnWriteArraySet

    CopyOnWriteArrayList相当于线程安全的ArrayList,内部存储结构采用Object[]数组,线程安全使用ReentrantLock实现,允许多个线程并发读取,但只能有一个线程写入。

    2.CopyOnWriteArrayList常用方法:

    (1)add()方法

               添加新元素

    1. /**
    2. * Appends the specified element to the end of this list.
    3. *
    4. * @param e element to be appended to this list
    5. * @return {@code true} (as specified by {@link Collection#add})
    6. */
    7. public boolean add(E e) {
    8. //使用ReentrantLock 加锁,保证线程安全
    9. final ReentrantLock lock = this.lock;
    10. lock.lock();
    11. try {
    12. Object[] elements = getArray();
    13. int len = elements.length;
    14. //复制出新数组
    15. Object[] newElements = Arrays.copyOf(elements, len + 1);
    16. //把新数组添加到复制的数组中
    17. newElements[len] = e;
    18. //吧原数组的引用指向新数组
    19. setArray(newElements);
    20. return true;
    21. } finally {
    22. lock.unlock();
    23. }
    24. }

    (2)get()方法

                  根据指定下标,到原数组中读取元素。允许多个线程并发读取。

    1. /**
    2. * {@inheritDoc}
    3. *
    4. * @throws IndexOutOfBoundsException {@inheritDoc}
    5. */
    6. public E get(int index) {
    7. //根据指定下标,从原数组中读取元素
    8. return get(getArray(), index);
    9. }
    10. @SuppressWarnings("unchecked")
    11. private E get(Object[] a, int index) {
    12. return (E) a[index];
    13. }

    (3)remove()方法

              删除指定下标元素,根据指定下标,从原数组中,Copy复制其他数组元素至新数组,最后调换数组。

    1. /**
    2. * Removes the element at the specified position in this list.
    3. * Shifts any subsequent elements to the left (subtracts one from their
    4. * indices). Returns the element that was removed from the list.
    5. *
    6. * @throws IndexOutOfBoundsException {@inheritDoc}
    7. */
    8. public E remove(int index) {
    9. final ReentrantLock lock = this.lock;
    10. lock.lock();
    11. try {
    12. Object[] elements = getArray();
    13. //获取数组长度
    14. int len = elements.length;
    15. //获取指定下标
    16. E oldValue = get(elements, index);
    17. int numMoved = len - index - 1;
    18. //判断删除的是否最后一个元素
    19. if (numMoved == 0)
    20. //如果是,复制原数组,长度-1
    21. setArray(Arrays.copyOf(elements, len - 1));
    22. else {
    23. //否则,创建一个新数组,长度-1
    24. Object[] newElements = new Object[len - 1];
    25. //复制一个除删除元素之外的新数组
    26. System.arraycopy(elements, 0, newElements, 0, index);
    27. System.arraycopy(elements, index + 1, newElements, index,
    28. numMoved);
    29. setArray(newElements);
    30. }
    31. return oldValue;
    32. } finally {
    33. lock.unlock();
    34. }
    35. }

    (4)set()方法

    set(index,element),按照指定下标,修改/添加元素。

    1. /**
    2. * Replaces the element at the specified position in this list with the
    3. * specified element.
    4. *
    5. * @throws IndexOutOfBoundsException {@inheritDoc}
    6. */
    7. public E set(int index, E element) {
    8. final ReentrantLock lock = this.lock;
    9. lock.lock();
    10. try {
    11. //获取源数组
    12. Object[] elements = getArray();
    13. E oldValue = get(elements, index);
    14. //判断新值和旧值是否相同
    15. if (oldValue != element) {
    16. int len = elements.length;
    17. //相同的话,不更新值,然后更新源数组(数组内容没变)
    18. Object[] newElements = Arrays.copyOf(elements, len);
    19. newElements[index] = element;
    20. setArray(newElements);
    21. } else {
    22. // 不同的话复制源数组,更新值,然后更新源数组
    23. setArray(elements);
    24. }
    25. return oldValue;
    26. } finally {
    27. lock.unlock();
    28. }
    29. }

    (5)clear方法

                清空所有元素

    1. /**
    2. * Removes all of the elements from this list.
    3. * The list will be empty after this call returns.
    4. */
    5. public void clear() {
    6. final ReentrantLock lock = this.lock;
    7. lock.lock();
    8. try {
    9. //创建一个新数组,并让其重新指向
    10. setArray(new Object[0]);
    11. } finally {
    12. lock.unlock();
    13. }
    14. }

    3.CopyOnWriteArrayList特性:

    • 在保证并发读取的前提下,确保了写入时的线程安全;(加锁ReentrantLock )
    • 由于每次写入操作时,进行了Copy复制原数组,所以无需扩容;(除读取数据外,其他复制操作会造成严重的内存开销)
    • 适合读多写少的应用场景。由于add()set()remove()等修改操作需要复制整个数组,所以会有内存开销大的问题。
    • CopyOnWriteArrayList由于只在写入时加锁,所以只能保证数据的最终一致性,不能保证数据的实时一致性
  • 相关阅读:
    CSS 滚动驱动动画 scroll-timeline ( scroll-timeline-name ❤️ scroll-timeline-axis )
    ckplayer自己定义风格播放器的开发记录
    ROS中的重名问题
    C#源代码生成器深入讲解二
    大文件分断上传后端
    Java配置37-搭建Nexus服务器
    Aspose.Cells实现excel预览
    【Vue】使用 Composition API 开发TodoList(2)
    Spring Boot分段处理List集合多线程批量插入数据
    数据分析 - 连接查询
  • 原文地址:https://blog.csdn.net/weixin_54535063/article/details/126921511