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

批量删除元素的流程,与上面类似:

  1. 先使用ReentrantLock加锁,保证线程安全 。
  2. 再创建一个新数组,长度暂时使用原数组的长度,因为不知道要删除多少个元素 。
  3. 然后遍历原数组 , 把需要保留的元素放到新数组中 。
  4. 释放掉新数组中空白位置,再使用新数组替换掉原数组 。
  5. 最后释放锁
如果遇到需要一次删除多个元素的场景 , 尽量使用 removeAll() 方法,因为 removeAll() 方法只涉及一次数组拷贝,性能比单个删除元素更好 。
并发修改问题【CopyOnWriteArrayList底层是怎么保证线程安全的?】当遍历CopyOnWriteArrayList的过程中 , 同时增删CopyOnWriteArrayList中的元素,会发生什么情况?测试一下:
import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class Test {    public static void main(String[] args) {        List<Integer> list = new CopyOnWriteArrayList<>();        list.add(1);        list.add(2);        list.add(2);        list.add(3);        // 遍历ArrayList        for (Integer key : list) {            // 判断如果元素等于2,则删除            if (key.equals(2)) {                list.remove(key);            }        }        System.out.println(list);    }}输出结果:
[1, 3]不但没有抛出异常,还把CopyOnWriteArrayList中重复的元素也都删除了 。原因是CopyOnWriteArrayList重新实现迭代器 , 拷贝了一份原数组的快照,在快照数组上进行遍历 。这样做的优点是其他线程对数组的并发修改,不影响对快照数组的遍历,但是遍历过程中无法感知其他线程对数组修改,有得必有失 。下面是迭代器的源码实现:
static final class COWIterator<E> implements ListIterator<E> {    /**     * 原数组的快照     */    private final Object[] snapshot;    /**     * 迭代游标     */    private int cursor;    private COWIterator(Object[] elements, int initialCursor) {        cursor = initialCursor;        snapshot = elements;    }    public boolean hasNext() {        return cursor < snapshot.length;    }    // 迭代下个元素    public E next() {        if (!hasNext())            throw new NoSuchElementException();        return (E)snapshot[cursor++];    }}总结现在可以回答文章开头提出的问题了吧:
  1. CopyOnWriteArrayList初始容量是多少?
答案:是0
  1. CopyOnWriteArrayList是怎么进行扩容的?
答案:
  • 加锁
  • 创建一个新数组,长度原数组长度+1,并把原数组元素拷贝到新数组里面 。
  • 释放锁
  1. CopyOnWriteArrayList是怎么保证线程安全的?
答案:
  • 使用ReentrantLock加锁 , 保证操作过程中线程安全 。
  • 使用volatile关键字修饰数组,保证当前线程对数组对象重新赋值后 , 其他线程可以及时感知到 。




推荐阅读