CopyOnWriteArrayList 源码剖析

CopyOnWriteArrayList&CopyOnWriteArraySet

基于jdk11
由于ArrayList操作并发下非线程安全,因为当一个线程在读,另外一个线程在写这样会造成线程不安全,因此引出了CopyOnWriteArrayList 也叫写时复制集合,而为什么要把CopyOnWriteArraySet也在这里说的主要是这个Set内部大部分是基于CopyOnWriteArrayList实现的,二者基本都是一致,只是一个可以存储重复数据一个不行,下面分析也是重点分析list

CopyOnWriteArrayList 是它支持并发情况下的使用,其内部的数据结构也是使用对象数组,通过同步锁和拷贝数组来保证并发安全。

根据它名称来分析就是写的时候复制,写时复制内部原理是当一个线程对集合进行add操作时,并不会直接在原数组添加,而是通过拷贝原数组到一个新数组后再进行add操作,同时在执行这个操作的时候还会在代码级别上加上同步锁(可以防止出现复制多个对象数组出现栈溢出),当完成添加数据后再把原对象数组指向这个新数组。这样进行读操作的时候不需要进行锁也不会出现并发操作。

下面开始看源码:

  1. 先看CopyOnWriteArrayList的全局遍历

    public class CopyOnWriteArrayList
        implements List, RandomAccess, Cloneable, java.io.Serializable {
        private static final long serialVersionUID = 8673264195747942595L;
    
        /**
         * 作为同步锁的对象
         */
        final transient Object lock = new Object();
    
        /*
        *存储数据的对象数组,注意这里用Volatile 可以禁止指令重排序,和可以使变量在线程之间是可见的,即a线程修改了改数组,B线程里面知道后进行更新
        */
        private transient volatile Object[] array;
    
    }
    
  2. CopyOnWriteArrayList的构造函数

        /**
         * 无参构造方法,会初始化一个大小尾0的对象数组。
         */
        public CopyOnWriteArrayList() {
            setArray(new Object[0]);
        }
    
        /**
         * 
         *带集合的构造方法
         * @param c the collection of initially held elements
         * @throws NullPointerException if the specified collection is null
         */
        public CopyOnWriteArrayList(Collection c) {
        
            Object[] es;
            //先判断c是否CopyOnWriteArrayList同种类型。
            if (c.getClass() == CopyOnWriteArrayList.class)
            //获取c的数据数组并赋值给es
                es = ((CopyOnWriteArrayList)c).getArray();
            else {
              //如果不同类型的化直接通过toArray调用系统底层system类获取数据数组
                es = c.toArray();
                // 如果类型不同则调用Arrays转换类型
                if (es.getClass() != Object[].class)
                    es = Arrays.copyOf(es, es.length, Object[].class);
            }
            setArray(es);
        }
    
        /**
         * 如果是数组则直接转换类型后就赋值给array数组
         */
        public CopyOnWriteArrayList(E[] toCopyIn) {
            setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
        }
    
    
  3. CopyOnWriteArrayList的添加数据操作

    它的添加操作是先拷贝一份原数组的数据到新数组,然后在这个新数组进行添加,添加完后把当前array数组指向这个新数组,以上操作均在同步锁内进行。、

    add有2个方法,一个是直接在数组末尾添加,一个是指定下标进行添加。

    
        public boolean add(E e) {
        //用同步锁保证线程安全
            synchronized (lock) {
              把当前数据赋值给es (相当于拷贝一份)
                Object[] es = getArray();
                //获取当前的数据长度
                int len = es.length;
                //进行数组拷贝并进行扩容
                es = Arrays.copyOf(es, len + 1);
                //添加元素到末尾
                es[len] = e;
                //把CopyOnWriteArrayList中的array指向es;
                setArray(es);
                return true;
            }
        }
    
        /**
         * 指向index进行添加
         */
        public void add(int index, E element) {
        //加同步锁保证线程安全
            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)//如果0即尾部添加
                    newElements = Arrays.copyOf(es, len + 1);
                else {
                //否则创建一个比原数据数组大一的对象数组
                    newElements = new Object[len + 1];
                    //先把es中的0~index的数据通过底层数组拷贝到新数组,
                    System.arraycopy(es, 0, newElements, 0, index);
                    //再次通过底层把es中index~index+1之间的数据拷贝的新数组
                    System.arraycopy(es, index, newElements, index + 1,
                                     numMoved);
                }
                //把待添加数据插入indexx
                newElements[index] = element;
               //把指向新数组
                setArray(newElements);
            }
        }
    
  4. 批量添加数据

        //批量添加一个Collection子类元素序列
        public boolean addAll(Collection c) {
        //判断类型是否相同,并且根据不同当情况获取c中的数据对象数组并赋值个新数组cs
            Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
                ((CopyOnWriteArrayList)c).getArray() : c.toArray();
            if (cs.length == 0)
                return false;
                //同步锁或独占锁控制线程安全
            synchronized (lock) {
               //把CopyOnWriteArrayList中的数据数组拷贝一份给es
                Object[] es = getArray();
                //获取es的长度并创建一个新数组,进行添加
                int len = es.length;
                Object[] newElements;
                //如果原数据数据为空则直接把cs赋值给新数值
                if (len == 0 && cs.getClass() == Object[].class)
                    newElements = cs;
                else {
                //否则先拷贝es并扩容为len+cs.length的对象数组中并赋值给新数组
                    newElements = Arrays.copyOf(es, len + cs.length);
                    在把需要添加的数据通过java底层库实现拷贝
                    System.arraycopy(cs, 0, newElements, len, cs.length);
                }
                setArray(newElements);
                return true;
            }
        }
    
        /**
         * 从指定位置开始添加
         * @see #add(int,Object)
         */
        public boolean addAll(int index, Collection 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)
                    return false;
             //以上操作和上面差不多
             //确认需要移动的个数,和创建新数组来存放这些数据
                int numMoved = len - index;
                Object[] newElements;
                if (numMoved == 0)//如果是尾部则直接扩容数据对象并赋值给新数组
                    newElements = Arrays.copyOf(es, len + cs.length);
                else {
                 //否则创建两个数据数组大小的新数组
                    newElements = new Object[len + cs.length];
                    //通过java底层拷贝es中0~index的数据到新数组中
                    System.arraycopy(es, 0, newElements, 0, index);
                    //再拷贝es中index~index+numMoved的数据到新数组下标为index+cs.length的位置之后
                    System.arraycopy(es, index,
                                     newElements, index + cs.length,
                                     numMoved);
                }
                //最后把需要添加的数据拷贝进新数组中
                System.arraycopy(cs, 0, newElements, index, cs.length);
                //把array指向新数组
                setArray(newElements);
                
                return true;
            }
        }
    
    
  5. 修改操作

        public E set(int index, E element) {
        //加同步锁
            synchronized (lock) {
            //拷贝数组
                Object[] es = getArray();
                //获取index的旧值
                E oldValue = elementAt(es, index);
                //修改并重新指向新数组
                if (oldValue != element) {
                    es = es.clone();
                    es[index] = element;
                    setArray(es);
                }
                return oldValue;
            }
        }
    
  6. 删除数据

    删除操作大体过程,在同步锁中先拷贝一份原数据数组到新数组中,然后计算要删除元素的位置到末尾需要移动的个数,因为删除后需要往前面移动(非尾部数据),其中删除元素是通过System调用底部类库来实现数组拷贝的。

       //删除index的元素
       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;
                //末尾的情况则之间调用Arrays的方法拷贝一些数组完事
                if (numMoved == 0)
                    newElements = Arrays.copyOf(es, len - 1);
                    //非尾部元素,需要分两段拷贝,先拷贝0~index到新数组中(不包括index)
                    //然后再拷贝index+1~尾部的数据到新数组中,然后把数据数组对象指向该新数组就好
                else {
                    newElements = new Object[len - 1];
                    System.arraycopy(es, 0, newElements, 0, index);
                    System.arraycopy(es, index + 1, newElements, index,
                                     numMoved);
                }
                setArray(newElements);
                return oldValue;
            }
        }
    
        /**
         * 根据数值移除
         */
        public boolean remove(Object o) {
        //拷贝数组
            Object[] snapshot = getArray();
            //调用indexOfRange获取该对象所在的下标
            int index = indexOfRange(o, snapshot, 0, snapshot.length);
            //再调用下面的remove方法
            return index >= 0 && remove(o, snapshot, index);
        }
        
       private static int indexOfRange(Object o, Object[] es, int from, int to) {
           //根据o的值判断在那个位置
           if (o == null) {
                for (int i = from; i < to; i++)
                    if (es[i] == null)
                        return i;
            } else {
                for (int i = from; i < to; i++)
                    if (o.equals(es[i]))
                        return i;
            }
            return -1;
        }
    
        /**
         *
         */
        private boolean remove(Object o, Object[] snapshot, int index) {
           //同步锁控制线程安全
           synchronized (lock) {
           //拷贝数据到current数组
                Object[] current = getArray();
                //获取当前数据长度
                int len = current.length;
                //两者不同时
                if (snapshot != current) 
                //证明在传递过程中发生了改变,重新找index。
                findIndex: {
                //那之前确认的index与当前数据数组的长度比较找较短那一个
                    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;
                        }
                    }
                    //判断index。
                    if (index >= len)
                        return false;
                    if (current[index] == o)
                        break findIndex;
                     //重新找index
                    index = indexOfRange(o, current, index, len);
                    if (index < 0)
                        return false;
                }
                //这里的删除和上面第一个remove操作是一致的
                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;
            }
        }
    
    
  7. 获取s数组倒数第一次出现元素的位置

     //根据元素值获取最后一次出现该元素的位置
     public int lastIndexOf(Object o) {
     //拷贝数组
            Object[] es = getArray();
            //调用lastIndexOfRange获取位置
            return lastIndexOfRange(o, es, 0, es.length);
        }
    
        /*
        *根据从index开始查找最后一次出现的e元素的位置
         */
        public int lastIndexOf(E e, int index) {
            Object[] es = getArray();
            return lastIndexOfRange(e, es, 0, index + 1);
        }
    //遍历查询小便
    private static int lastIndexOfRange(Object o, Object[] es, int from, int to) {
            if (o == null) {
                for (int i = to - 1; i >= from; i--)
                    if (es[i] == null)
                        return i;
            } else {
                for (int i = to - 1; i >= from; i--)
                    if (o.equals(es[i]))
                        return i;
            }
            return -1;
        }
    
  8. 介绍一下CopyOnWriteArraySet这个也是写时复制的容器但是是set类型即不能存储重复数据的,但其内部只是存储数据也是使用CopyOnWriteArrayList来存储只是部分方法调用需要进行判断是否存在,这些方法都在CopyOnWriteArrayList中实现的

    首先是添加元素方法,如果已经存在则不添加

        //先拷贝一份数据到snapshot数组
        public boolean addIfAbsent(E e) {
         //拷贝数据的快照数组中
            Object[] snapshot = getArray();
            //先判断e元素是否存在如果存在直接返回失败,否则不存在在在进行调用添加方法
            return indexOfRange(e, snapshot, 0, snapshot.length) < 0
                && addIfAbsent(e, snapshot);
        }
    
        /**
         * 这个方法是set实际添加元素的方法,里面有进行判断元素是否存在
         */
        private boolean addIfAbsent(E e, Object[] snapshot) {
        //同步锁
            synchronized (lock) {
            //再次拷贝当前数据数组
                Object[] current = getArray();
                int len = current.length;
                //如果前后不一致说明有线程修改了则再次判断
                if (snapshot != current) {
                    //再次判断之前快照数组中是否存在该数据
                    int common = Math.min(snapshot.length, len);
                    for (int i = 0; i < common; i++)
                        if (current[i] != snapshot[i]
                            && Objects.equals(e, current[i]))
                            return false;
                      //如果快照数组中不存在则遍历当前数组中后面部分,如果不存在则添加
                    if (indexOfRange(e, current, common, len) >= 0)
                            return false;
                }
                //添加元素操作
                Object[] newElements = Arrays.copyOf(current, len + 1);
                newElements[len] = e;
                setArray(newElements);
                return true;
            }
        }
    
  9. 批量添加操作也是基于Set的

    
        public int addAllAbsent(Collection c) {
        //获取c序列的对象数据数组
            Object[] cs = c.toArray();
            if (cs.length == 0)
                return 0;
            synchronized (lock) {
            //获取当前Set的数据对象数组
                Object[] es = getArray();
                int len = es.length;
                int added = 0;//记录添加成功的个数
                //这里判断cs里面的元素是否存在,如果不存在则直接添加
                for (int i = 0; i < cs.length; ++i) {
                    Object e = cs[i];
                    //这里进行了两次判断,一次是判断当前set中是否存在,还有一次是判断当前元素是否在cs前面已经添加过了。如果两者都成立则添加
                    if (indexOfRange(e, es, 0, len) < 0 &&
                        indexOfRange(e, cs, 0, added) < 0)
                        cs[added++] = e;
                }
                //如果有添加数据则重新拷贝到新数组并重新指向该新数组
                if (added > 0) {
                    Object[] newElements = Arrays.copyOf(es, len + added);
                    System.arraycopy(cs, 0, newElements, len, added);
                    setArray(newElements);
                }
                return added;
            }
        }
    
  10. CopyOnWriteArraySet中判断一个序列是否是set中的子序列

      //先进行类型判断如果不是同类型则跳到list中的containsAll进行判断是否完全存在于集合里面,
      //如果类型现它则调用比较方法 
      public boolean containsAll(Collection c) {
            return (c instanceof Set)
                ? compareSets(al.getArray(), (Set) c) >= 0
                : al.containsAll(c);
        }
        //该方法jdk1.8是不存在的
      //这里是比较当前数据对象数组中是否包含c的  
      //1是set是 snapshot的超集,0是两者元素相同,-1是不相同
        private static int compareSets(Object[] snapshot, Set set) {
       
      // Uses O(n^2) algorithm, that is only appropriate for small
            // 
    
            final int len = snapshot.length;
            // 数组用来判断是否匹配用的
            final boolean[] matched = new boolean[len];
    
            // 
            int j = 0;
            outer: for (Object x : set) {
                for (int i = j; i < len; i++) {
                //如果set中的数值和快照数组中的值相同则设置指定位置中元素匹配matched[i]为true
                    if (!matched[i] && Objects.equals(x, snapshot[i])) {
                        matched[i] = true;    
                        
                        if (i == j)
                            do { j++; } while (j < len && matched[j]);
                        continue outer;
                    }
                }
                return -1;
            }
            return (j == len) ? 0 : 1;
        }
        
        //CopyOnWriteArrayList中的用于判断是否包含c序列如果发现一个不存在立刻返回
        public boolean containsAll(Collection c) {
            Object[] es = getArray();
            int len = es.length;
            for (Object e : c) {
                if (indexOfRange(e, es, 0, len) < 0)
                    return false;
            }
            return true;
        }
    
    

    其他那些方法类似就不分析了。

    总结:CopyOnWriteArrayList和CopyOnWriteArrayList是juc包下的一种支持并发的容器,它的优点是读写都是线程安全的,而且读取速率快,其中通过独占锁(或同步锁)在代码级别上来控制线程安全,但因为写时复制,因此如果原理数据就很大了再拷贝一份,就很吃内存,此外,读取速率是很快读取的会出现类似于幻读/不可重读这样的情况,也就是说数据可能不是最新的,也就是说只能写时复制只是保证了最终一致性,总体感觉二者区别就在于是否可以重复存储元素,另外上面是基于jdk11分析,对比了一下jdk1.8感觉差别也不大,就添加了个别方法
    HashTable源码分析
    LinkedList源码分析
    Vector源码分析
    CopyOnWriteArrayList源码分析
    SynchorincedList源码分析

你可能感兴趣的:(源码系列,Java系列)