美文网首页程序员
jdk源码CopyOnWriteArrayList分析基于JDK

jdk源码CopyOnWriteArrayList分析基于JDK

作者: 超级小学生1 | 来源:发表于2018-09-16 18:55 被阅读12次

    简介

    在通过介绍 Vector 时提到了 CopyOnWriteArrayList 类。它也属于Collection 家族下的一个线程安全的一个集合,而且同样基于对象数组实现。所以,目前基于对象数组实现的目前接触到了 ArraylistVector ,和当前介绍的CopyOnWriteArrayList。而且,三者也可以拿来对比一下各自的应用场景。

    1.首先,CopyOnWriteArrayList基于对象数组,但是并不意味着增删比较快(重点),其应用场景在于读多写少情况(埋个伏笔)。

    2.CopyOnWriteArrayList实现了 RandmoAccess接口。(同之前一样,就是为了实现快速访问,在ArrayList和vector中已经提出过)。

    3.CopyOnWriteArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

    4.CopyOnWriteArrayList 实现java.io.Serializable接口,支持序列化。

    5.CopyOnWriteArrayList,是线程安全的。注意 这里单独是写入操作是线程安全的。

    通过上面的几点(如果你看了前三节总结的知识点),首先可以总结到 Collection 接口下,很多的工具类提供了很多相似的功能,绝大部分实现了,Cloneable接口,java.io.Serializable接口,RandomAccess接口。这里体现出了一点,面向接口的编程,更便于扩展。不同的工具类只需扩展对应的接口,然后接下来实现关于自己的特性即可。这点非常的重要,这也是延伸出来可以学习到更重要的知识。

    继承实现体系:

    BE70E0D5-5AC6-4FF0-AB80-4396BD8AFAAE.png

    构造函数

    共有三个构造函数,分别为:

    1.无参数

    /**
     * 创建一个空的列表
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    

    2.以一个 Collection 为参数构造一个新的列表。

     /**
     * 创建包含指定元素的列表集合,按照集合返回的顺序的迭代器.
     *
     * @param c the collection of initially held elements
     * @throws NullPointerException if the specified collection       * is null
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        //这里是判断是否为同一个类型。
        //c.getClass()返回包路径,和当前class名称。
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
              //转为array,然后调用Arrays.copyOf(),来构造新的list
            elements = c.toArray();
            // defend against c.toArray (incorrectly) not returning Object[]
            // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652)
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        //这里将返回的新的Array复制给全局变量array
        setArray(elements);
    }
    
     **
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }
    
    /** The array, accessed only via getArray/setArray. */
    //volatile关键字,代表此array修改之后立刻对其他线程可见。
    //但是要注意,在CopyOnWriteArrayList内,对array的操作都是通过setArray方法。
    //因为有一个统一设置array的入口,当array改变后,可以直接对其它线程可见。
    private transient volatile Object[] array;
    

    3. 以数组作为参数,造一个新的列表。

      /**
     * 通过一个数组来穿件集合
     *
     * @param toCopyIn the array (a copy of this array is used as the
     *        internal array)
     * @throws NullPointerException if the specified array is null
     */
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
    

    常用方法

    1.add()方法

     /**
     * 添加一个节点在集合末尾
     *
     * @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[] elements = getArray();
            int len = elements.length;
            //复制原来的集合
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //设置新的节点内容,在集合末尾
            newElements[len] = e;
            //将array引用指向到新数组
            setArray(newElements);
            return true;
        }
    }
    
    
    
     /**
     * 添加元素到指定索引
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
          //同样线程安全
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException(outOfBounds(index, len));
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                    //numMoved为0说明index为当前链表末尾
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                //创建新的数组,长度为原来素组长度+1
                newElements = new Object[len + 1];
                //复制,index之前数组元素
                System.arraycopy(elements, 0, newElements, 0, index);
                //复制index之后元素
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //设置index处元素
            newElements[index] = element;
            // 将array引用指向到新数组
            setArray(newElements);
        }
    }   
    

    注意

    通过add方法应该意识到,每当有元素添加的操作时。总是先把当前的集合复制一份,然后返回并且添加新的元素到当前集合,然后才调用setArray方法,使新的集合生效。这也就意味着,CopyOnWriteArrayList的增加元素操作极为耗资源。同比,ArrayList他是提前把数组的长度做扩容,然后将新的元素添加到数组的末尾,但是CopyOnWriteArrayList不能这么做。原因在于,在每次的数据改动时,因为为了避免影响读操作,都需要copy一个原有集合的副本,当操作完毕将引用指向新的集合对象。而并不是在原有的对象上做数据的改动处理,因此扩容也就没有意义。

    remove()

    /**
     * 删除集合内指定索引处的元素
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        synchronized (lock) {
            Object[] elements = getArray();
            int len = elements.length;
            //需要删除的元素
            E oldValue = elementAt(elements, index);
            //元素需要删除的索引信息
            int numMoved = len - index - 1;
            if (numMoved == 0)
                   //numMoved为0,删除最后一个节点
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                //创建新的数组对象
                Object[] newElements = new Object[len - 1];
                //复制index之前的元素
                System.arraycopy(elements, 0, newElements, 0, index);
                //将index后的元素,从index开始替换。
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                //将array引用指向到新数组
                setArray(newElements);
            }
            //返回删除的元素
            return oldValue;
        }
    }
    

    get()

    /**
     * 读操作。
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return elementAt(getArray(), index);
    }
    

    注意:唯一你需要注意到的就是这里没有添加关于线程安全的 synchronized 关键字。

    总结

    1.CopyOnWriteArrayList 数组有改动,都需要耗费过多的资源用来拷贝数组的副本。因此,读多写少的场景最适合应用,这个就要看具体的业务了。

    2.CopyOnWriteArrayList 添加或者删除操作,并不能保证达到实时读。因为数据的引用没有指向新的对象之前,读的仍是旧的数据。(setArray方法调用之前读的仍然是旧数据)。

    3.这里更好的体现了一个思想时,读写分离。在多线程应用下,达到线程安全。并且保证数据的一致性。

    4.多线程情况下,采用对象副本的方式解决了线程并发冲突问题。

    相关文章

      网友评论

        本文标题:jdk源码CopyOnWriteArrayList分析基于JDK

        本文链接:https://www.haomeiwen.com/subject/ufufnftx.html