CopyOnWriteArrayList底层是怎么保证线程安全的?( 二 )

  • 最后释放锁
  • add() 方法添加元素的时候,并没有在原数组上进行赋值,而是创建一个新数组,在新数组上赋值后,再用新数组替换原数组 。这是为了利用volatile关键字的特性 , 如果直接在原数组上进行修改,其他线程是感知不到的 。只有重新对原数组对象进行赋值,其他线程才能感知到 。还有一个需要注意的点是,每次添加元素的时候都会创建一个新数组,并涉及数组拷贝,相当于每次都进行扩容操作 。当数组较大 , 性能消耗较为明显 。所以CopyOnWriteArrayList适用于读多写少的场景 , 如果存在较多的写操作场景,性能也是一个需要考虑的因素 。
    删除元素再看一下删除元素的方法 remove() 的源码:
    // 按照下标删除元素public E remove(int index) {    // 加锁,保证线程安全    final ReentrantLock lock = this.lock;    lock.lock();    try {        // 获取原数组        Object[] elements = getArray();        int len = elements.length;        E oldValue = get(elements, index);        // 计算需要移动的元素个数        int numMoved = len - index - 1;        if (numMoved == 0) {            // 0表示删除的是数组末尾的元素            setArray(Arrays.copyOf(elements, len - 1));        } else {            // 创建一个新数组,长度是原数组长度-1            Object[] newElements = new Object[len - 1];            // 把原数组下标前后两段的元素都拷贝到新数组里面            System.arraycopy(elements, 0, newElements, 0, index);            System.arraycopy(elements, index + 1, newElements, index,                numMoved);            // 替换原数组            setArray(newElements);        }        return oldValue;    } finally {        // 释放锁        lock.unlock();    }}删除元素的流程:
    1. 先使用ReentrantLock加锁,保证线程安全 。
    2. 再创建一个新数组,长度是原数组长度-1 , 并把原数组中剩余元素(不包含需要删除的元素)拷贝到新数组里面 。
    3. 使用新数组替换掉原数组
    4. 最后释放锁
    可以看到,删除元素的流程与添加元素的流程类似,都是需要创建一个新数组,再把旧数组元素拷贝到新数组,最后替换旧数组 。区别就是新数组的长度不一样,删除元素流程中的新数组长度是旧数组长度-1,添加元素流程中的新数组长度是旧数组长度+1 。根据对象删除元素的方法源码与之类似,也是转换成下标删除,读者可自行查看 。
    批量删除再看一下批量删除元素方法 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) {            // 创建一个新数组,长度暂时使用原数组的长度 , 因为不知道要删除多少个元素 。            Object[] temp = new Object[len];            // newlen表示新数组中元素个数            int newlen = 0;            // 遍历原数组,把需要保留的元素放到新数组中            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;            }        }        return false;    } finally {        // 释放锁        lock.unlock();    }}


    推荐阅读