【从入门到放弃-Java】并发编程-JUC-CopyOnWriteArrayList

简介: 前言上文【从入门到放弃-Java】并发编程-JUC-ConcurrentHashMap中,我们学习了常用的并发容器CurrentHashMap,本文我们来了解下List的并发容器:CopyOnWriteArrayList直接来看源码。

前言

上文【从入门到放弃-Java】并发编程-JUC-ConcurrentHashMap中,我们学习了常用的并发容器CurrentHashMap,本文我们来了解下List的并发容器:CopyOnWriteArrayList
直接来看源码。

CopyOnWriteArrayList

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) {
    //使用synchronized加锁
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        //拷贝一份array,数组大小加一
        es = Arrays.copyOf(es, len + 1);
        //设置最后一位为需要添加的值e
        es[len] = e;
        //将新的array设置为当前List的中的值,array是volatile类型的,因此写入后其它线程能立即看到
        setArray(es);
        return true;
    }
}

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    //使用synchronized加锁
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException(outOfBounds(index, len));
        Object[] newElements;
        int numMoved = len - index;
        
        if (numMoved == 0)
            //如果是在尾部插入,则将List中的数组直接copy并将长度加一
            newElements = Arrays.copyOf(es, len + 1);
        else {
            //如果不是在尾部插入,则以index处将原array分割成两部分copy到新数组中,空出index的位置
            newElements = new Object[len + 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index, newElements, index + 1,
                             numMoved);
        }
        //在index出设置element的值
        newElements[index] = element;
        setArray(newElements);
    }
}

addAll

/**
 * Appends all of the elements in the specified collection to the end
 * of this list, in the order that they are returned by the specified
 * collection's iterator.
 *
 * @param c collection containing elements to be added to this list
 * @return {@code true} if this list changed as a result of the call
 * @throws NullPointerException if the specified collection is null
 * @see #add(Object)
 */
public boolean addAll(Collection<? extends E> c) {
    //获取数组
    Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
        ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
    if (cs.length == 0)
        return false;
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        Object[] newElements;
        
        if (len == 0 && cs.getClass() == Object[].class)
            //如果当前List长度为0,则直接设置array为新数组
            newElements = cs;
        else {
            //将原数组copy为新的数组,新的数组长度为len+cs.lenth
            newElements = Arrays.copyOf(es, len + cs.length);
            //从len处开始,设置为需要添加的数组
            System.arraycopy(cs, 0, newElements, len, cs.length);
        }
        //将值写入List的成员变量array,array是volatile类型的,因此写入后其它线程能立即看到
        setArray(newElements);
        return true;
    }
}

/**
 * Inserts all of the elements in the specified collection into this
 * list, starting at the specified position.  Shifts the element
 * currently at that position (if any) and any subsequent elements to
 * the right (increases their indices).  The new elements will appear
 * in this list in the order that they are returned by the
 * specified collection's iterator.
 *
 * @param index index at which to insert the first element
 *        from the specified collection
 * @param c collection containing elements to be added to this list
 * @return {@code true} if this list changed as a result of the call
 * @throws IndexOutOfBoundsException {@inheritDoc}
 * @throws NullPointerException if the specified collection is null
 * @see #add(int,Object)
 */
public boolean addAll(int index, Collection<? extends E> c) {
    //获取数组
    Object[] cs = c.toArray();
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException(outOfBounds(index, len));
        if (cs.length == 0)
            //如果当前List长度为0,直接返回false插入失败
            return false;
        int numMoved = len - index;
        Object[] newElements;
        if (numMoved == 0)
            //如果List尾部插入,则直接在尾部copy要插入的数组
            newElements = Arrays.copyOf(es, len + cs.length);
        else {
            //如果不是在尾部插入,则以index处将原array分割成两部分copy到新数组中,空出index到index+cs.length长度的位置
            newElements = new Object[len + cs.length];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index,
                             newElements, index + cs.length,
                             numMoved);
        }
        //将index到index+cs.length填充需要插入的数组
        System.arraycopy(cs, 0, newElements, index, cs.length);
        setArray(newElements);
        return true;
    }
}

get

//get方式就比较简单了 因为array是volatile类型的,不需要任何同步操作就可取到值,保证了并发
public E get(int index) {
    return elementAt(getArray(), index);
}
static <E> E elementAt(Object[] a, int index) {
    return (E) a[index];
}

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) {
    //同步锁
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        E oldValue = elementAt(es, index);
        int numMoved = len - index - 1;
        Object[] newElements;
        if (numMoved == 0)
            //如果是移除最后一个元素,则直接copy 0到len-2位置的元素即可
            newElements = Arrays.copyOf(es, len - 1);
        else {
            //如果不是最后一个元素,则copy需要删除数组[0,index)和[index,length]的元素到新数组。
            newElements = new Object[len - 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index + 1, newElements, index,
                             numMoved);
        }
        setArray(newElements);
        return oldValue;
    }
}

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If this list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * {@code i} such that {@code Objects.equals(o, get(i))}
 * (if such an element exists).  Returns {@code true} if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *
 * @param o element to be removed from this list, if present
 * @return {@code true} if this list contained the specified element
 */
public boolean remove(Object o) {
    Object[] snapshot = getArray();
    //找到需要删除元素的索引
    int index = indexOfRange(o, snapshot, 0, snapshot.length);
    return index >= 0 && remove(o, snapshot, index);
}

/**
 * A version of remove(Object) using the strong hint that given
 * recent snapshot contains o at the given index.
 */
private boolean remove(Object o, Object[] snapshot, int index) {
    synchronized (lock) {
        Object[] current = getArray();
        int len = current.length;
        //加锁后验证数组是否在找到要删除的索引后改动过,如果改动的话,删除改动后的第一个o元素
        if (snapshot != current) findIndex: {
            int prefix = Math.min(index, len);
            for (int i = 0; i < prefix; i++) {
                if (current[i] != snapshot[i]
                    && Objects.equals(o, current[i])) {
                    index = i;
                    break findIndex;
                }
            }
            if (index >= len)
                return false;
            if (current[index] == o)
                break findIndex;
            index = indexOfRange(o, current, index, len);
            if (index < 0)
                return false;
        }
        //删除index位置的元素
        Object[] newElements = new Object[len - 1];
        System.arraycopy(current, 0, newElements, 0, index);
        System.arraycopy(current, index + 1,
                         newElements, index,
                         len - index - 1);
        setArray(newElements);
        return true;
    }
}

iterator

/**
 * Returns an iterator over the elements in this list in proper sequence.
 *
 * <p>The returned iterator provides a snapshot of the state of the list
 * when the iterator was constructed. No synchronization is needed while
 * traversing the iterator. The iterator does <em>NOT</em> support the
 * {@code remove} method.
 *
 * @return an iterator over the elements in this list in proper sequence
 */
public Iterator<E> iterator() {
    //和ArrayList不同,CopyOnWriteArrayList在iterator遍历时,是对当前的array做了一个快照,在遍历期间,array可能会被别的线程更改,但快照不会改变,不受影响,因此在迭代中,数组元素不能改。
    return new COWIterator<E>(getArray(), 0);
}

总结

通过源码分析我们了解到,CopyOnWriteArrayList写操作时,都是以加synchronized锁并copy一份数组进行修改的方式进行的,如果List比较大时,会非常占用资源。

读操作时不用加锁,因为array是volatile的,不需要额外同步,因此读性能非常高。

因此CopyOnWriteArrayList更适用于读多写少的并发操作中。

在遍历时,将当前的array做一个快照,不受其他线程更改的影响。因此,在iterator中,list中的元素是不能更改的(因为更改的是快照,不会写到原数组中,更改一定是无效的)。

但在for循环时,CopyOnWriteArrayList中的元素可以被更改,而ArrayList不行,因为ArrayList的元素被遍历到时总会先检查是否被更改,更改会抛出ConcurrentModificationException

更多文章

见我的博客:https://nc2era.com

written by AloofJr,转载请注明出处

目录
相关文章
|
7天前
|
Java 编译器 开发者
深入理解Java内存模型(JMM)及其对并发编程的影响
【9月更文挑战第37天】在Java的世界里,内存模型是隐藏在代码背后的守护者,它默默地协调着多线程环境下的数据一致性和可见性问题。本文将揭开Java内存模型的神秘面纱,带领读者探索其对并发编程实践的深远影响。通过深入浅出的方式,我们将了解内存模型的基本概念、工作原理以及如何在实际开发中正确应用这些知识,确保程序的正确性和高效性。
|
2月前
|
Java 程序员 调度
【JAVA 并发秘籍】进程、线程、协程:揭秘并发编程的终极武器!
【8月更文挑战第25天】本文以问答形式深入探讨了并发编程中的核心概念——进程、线程与协程,并详细介绍了它们在Java中的应用。文章不仅解释了每个概念的基本原理及其差异,还提供了实用的示例代码,帮助读者理解如何在Java环境中实现这些并发机制。无论你是希望提高编程技能的专业开发者,还是准备技术面试的求职者,都能从本文获得有价值的见解。
48 1
|
5天前
|
开发框架 IDE Java
java制作游戏,如何使用libgdx,入门级别教学
本文是一篇入门级教程,介绍了如何使用libgdx游戏开发框架创建一个简单的游戏项目,包括访问libgdx官网、设置项目、下载项目生成工具,并在IDE中运行生成的项目。
20 1
java制作游戏,如何使用libgdx,入门级别教学
|
18天前
|
Java 开发者
深入探索Java中的并发编程
本文将带你领略Java并发编程的奥秘,揭示其背后的原理与实践。通过深入浅出的解释和实例,我们将探讨Java内存模型、线程间通信以及常见并发工具的使用方法。无论是初学者还是有一定经验的开发者,都能从中获得启发和实用的技巧。让我们一起开启这场并发编程的奇妙之旅吧!
|
21天前
|
Java 程序员 UED
Java中的异常处理:从入门到精通
【9月更文挑战第23天】在Java编程的世界中,异常是程序执行过程中不可避免的事件,它们可能会中断正常的流程并导致程序崩溃。本文将通过浅显易懂的方式,引导你理解Java异常处理的基本概念和高级技巧,帮助你编写更健壮、更可靠的代码。我们将一起探索如何捕获和处理异常,以及如何使用自定义异常来增强程序的逻辑和用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供有价值的见解和实用的技巧。
34 4
|
20天前
|
算法 安全 Java
Java中的并发编程是如何实现的?
Java中的并发编程是通过多线程机制实现的。Java提供了多种工具和框架来支持并发编程。
15 1
|
1月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
1月前
|
安全 Java 测试技术
掌握Java的并发编程:解锁高效代码的秘密
在Java的世界里,并发编程就像是一场精妙的舞蹈,需要精准的步伐和和谐的节奏。本文将带你走进Java并发的世界,从基础概念到高级技巧,一步步揭示如何编写高效、稳定的并发代码。让我们一起探索线程池的奥秘、同步机制的智慧,以及避免常见陷阱的策略。
|
2月前
|
算法 Java 开发者
Java 编程入门:从零到一的旅程
本文将带领读者开启Java编程之旅,从最基础的语法入手,逐步深入到面向对象的核心概念。通过实例代码演示,我们将一起探索如何定义类和对象、实现继承与多态,并解决常见的编程挑战。无论你是编程新手还是希望巩固基础的开发者,这篇文章都将为你提供有价值的指导和灵感。
|
2月前
|
存储 Java 程序员
Java中的集合框架:从入门到精通
【8月更文挑战第30天】在Java的世界里,集合框架是一块基石,它不仅承载着数据的存储和操作,还体现了面向对象编程的精髓。本篇文章将带你遨游Java集合框架的海洋,从基础概念到高级应用,一步步揭示它的奥秘。你将学会如何选择合适的集合类型,掌握集合的遍历技巧,以及理解集合框架背后的设计哲学。让我们一起探索这个强大工具,解锁数据结构的新视角。