并发容器之CopyOnWriteArrayList和CopyOnWriteArraySet

CopyOnWriteArrayList

一、类结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gl6NVgDf-1585574481170)(C:\Users\MI\AppData\Roaming\Typora\typora-user-images\1585219561302.png)]

可以看到其自左向右实现了如下接口:

  1. Cloneable:可以实现克隆
  2. List:实现了List中的所有操作
  3. RnadomAccess:随机访问
  4. Setializable:可序列化

二、类属性

 /** 这里使用可重入锁 */
    final transient ReentrantLock lock = new ReentrantLock();

    /**存储数据的数组 */
    private transient volatile Object[] array;

三、构造函数

3.1 默认构造

 public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
final void setArray(Object[] a) {
        array = a;
}

默认构造默认初始化一个长度为0的空数组

3.2其他构造

public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
    	//如果c是CopyOnWriteArrayList的,则直接转为数组赋值
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            //否则转为数组
            elements = c.toArray();
           //如果该数组不是Object类型的,则拷贝为Object类型数组后赋值
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

**CopyOnWriteArrayList(E[] toCopyIn) **构造

将toCopyIn拷贝为Object类型的数组传入

    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

四、方法

4.1 add(E x)

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);
            //加入新的元素
            newElements[len] = e;
            //新数组替换掉旧数组
            setArray(newElements);
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }
  1. 先上锁
  2. 获取原数组进行拷贝,长度+1
  3. 将新元素加到最后
  4. 替换掉旧数组
  5. 释放锁

4.2 add(int index, E element)

public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
     	//1. 上锁
        lock.lock();
        try {
            //获取原数组
            Object[] elements = getArray();
            int len = elements.length;
            //检索index
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            //新数组
            Object[] newElements;
            //要移动的位置的起点,将index往后的元素都要移动
            int numMoved = len - index;
            if (numMoved == 0)//如果要在第len个位置添加,则数组长度不够,拷贝原数组,长度+1
                newElements = Arrays.copyOf(elements, len + 1);
            else {//要在数组中间插入
                //新建一个长度为len+1的数组
                newElements = new Object[len + 1];
                //将index前的元素拷贝到新数组
                System.arraycopy(elements, 0, newElements, 0, index);
                //将index及其以后的元素拷贝到新数组index+1开始的位置,这样留出第index位置插入新元素
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //插入新元素
            newElements[index] = element;
            setArray(newElements);
        } finally {
            //释放锁
            lock.unlock();
        }
    }

流程:

  1. 先上锁,再检验下标index
  2. 如果在最后插入,则拷贝数组,长度加1
  3. 如果在中间插入,则拷贝新数组,将旧数组元素以index为界,分别拷贝到新数组的[0,index-1]和[index+1,…]位置,留出第index位置
  4. 将新元素放在新数组的index位置后覆盖旧数组
  5. 释放锁

addAll方法和以上两个逻辑类似,不赘述

4.3 get()

 public E get(int index) {
        return get(getArray(), index);
    }
 private E get(Object[] a, int index) {
        return (E) a[index];
    }

直接返回数组index位置元素

4.4 remove()

public E remove(int index) {
        final ReentrantLock lock = this.lock;
    //上锁
        lock.lock();
        try {
            //旧数组
            Object[] elements = getArray();
            int len = elements.length;
            //index位置元素
            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];
                //将index以前的元素拷贝到新数组
                System.arraycopy(elements, 0, newElements, 0, index);
                //将index以后的数组拷贝到新数组,这样index位置的元素就移除了
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                //赋值数组
                setArray(newElements);
            }
            //返回删除的元素
            return oldValue;
        } finally {
            //释放锁
            lock.unlock();
        }
    }
  1. 加锁
  2. 校验index
  3. 如果删除最后一个元素,则将最后一个元素之前的拷贝到新数组并覆盖旧数组
  4. 如果删除中间元素,则创建新数组,将index之前和之后的元素分别拷贝到新数组,这样就移除了index位置的元素,最后新数组覆盖旧数组
  5. 返回被删除的元素值
  6. 释放锁

4.4 removeAll()

 public boolean removeAll(Collection<?> c) {
        if (c == null) throw new NullPointerException();
        final ReentrantLock lock = this.lock;
        lock.lock();//加锁
        try {
            //原数组
            Object[] elements = getArray();
            int len = elements.length;
            if (len != 0) {//原数组有元素
               
                int newlen = 0;
                //构造长度为len的临时数组
                Object[] temp = new Object[len];
                for (int i = 0; i < len; ++i) {
                    //遍历旧数组,将不在被删除集合的元素加入到临时数组
                    Object element = elements[i];
                    if (!c.contains(element))
                        temp[newlen++] = element;
                }
                if (newlen != len) {
                    //覆盖旧数组
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;
                }
            }
            //原数组为空,返回false
            return false;
        } finally {
            lock.unlock();
        }
    }
  1. 加锁
  2. 如果原数组有值,则构造新的数组,遍历旧数组,将不需要删除的元素放入新数组,最后覆盖旧数组,返回true
  3. 如果原数组为空,则返回false
  4. 释放锁

4.5 addIfAbsent(E e)

如果该元素不在集合中,则添加该元素

 public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();//获取原数组
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false ://如果原数组不存在该元素,则返回false
            addIfAbsent(e, snapshot);
    }
private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();//上锁
        try {
            //再次获取旧数组
            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] && eq(e, current[i]))//i位置的元素已改变,且是当前元素,已存在,返回false
                        return false;
                
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            //构造一个长度为n+1的数组
            Object[] newElements = Arrays.copyOf(current, len + 1);
            //添加新元素
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

分析完后,会发现除了读操作,修改、删除的思想基本一致。

五、总结

  1. 内部使用ReentrantLock重入锁实现线程安全
  2. 构造函数默认初始化数组长度为0
  3. 读操作不加锁,直接返回对应位置元素
  4. 写操作和读操作,都是通过先加锁,再拷贝旧数组,新数组长度为添加后的长度或删除后的长度,(数组长度永远是元素的个数),最后再替换掉旧数组,释放锁
  5. 该并发容器适合读多写少的场景。修改删除都需要拷贝,代价高
  6. 由于其读写分离的思想,因此读的时候可能会读到旧值,只能保证数据的最终一致性
  7. 写时复制会导致如果对象过大,会让内存同时存在两个大数组,就会触发youngGC或FullGC,导致STW,降低性能

CopyOnWriteArraySet

一、类属性

private final CopyOnWriteArrayList<E> al;

可见其底层是基于CopyOnWriteArrayList实现

二、构造函数

//默认构造直接使用 CopyOnWriteArrayList的默认构造
public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }

  	//
    public CopyOnWriteArraySet(Collection<? extends E> c) {
        if (c.getClass() == CopyOnWriteArraySet.class) {//如果是CopyOnWriteArraySet类型就直接将调用其参数为集合的构造
            @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
                (CopyOnWriteArraySet<E>)c;
            al = new CopyOnWriteArrayList<E>(cc.al);
        }
        else {
            //如果不是,则调用addAllAbsent方法将c添加到集合中,如果已经存在(即有重复的)就不会添加
            al = new CopyOnWriteArrayList<E>();
            al.addAllAbsent(c);
        }
    }

三、add

  public boolean add(E e) {
        return al.addIfAbsent(e);
    }

内部调用CopyOnWriteArrayListaddIfAbsent方法,这也就知道了,如果元素已存在,就不会添加进入,也就保证了唯一性。

而CopyOnWriteArraySet的其他方法都是使用CopyOnWriteArrayList的方法

发布了75 篇原创文章 · 获赞 13 · 访问量 8369

猜你喜欢

转载自blog.csdn.net/weixin_43696529/article/details/105209005