Java CopyOnWriteArrayList

1. 为何须要 CopyOnWriteArrayList数组

ArrayList 的内部实现是一个数组, 而且是动态扩容的, 当插入数据时, 先判断数组是否须要扩容, 若是须要扩容, 则先扩容, 再插入数据, 也就说插入由三步组成并发

1) 检查是否须要扩容spa

2) 扩容/不扩容线程

3) 数据加入到数组code

代码以下blog

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

这里若是出现并发操做, 会有两个问题ci

1) 若是同时进行扩容, 则有可能出现连续进行两次扩容的问题, 而实际只须要一次element

2) 若是同时对数组进行赋值, 则有可能第一个赋值元素被覆盖, 由于可能两个线程拿到的 size 是同样的, 他们都填到数组的同一个槽里rem

再看另外一个 add 操做get

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

这种状况下, add 分为四步

1) 检查是否须要扩容

2) 扩容

3) 移动数据

4) 插入数据

若是此时有并发的读取和插入操做, 则有可能出现读取到的值为 null 的状况, 例如 list.get(3) 跟 list.add(3, "new") 同时发生, 原本 list.get(3) 应该拿到 "old" 或者 "new", 如今却拿到了 null, 这是由于在取值的过程当中正好发生了移动数据, 可是数据又还没被插入到移动的空槽里

 

2. 如何解决这些问题?

一种最简单的方式是对 ArrayList 的全部行为所有加锁, 例如 Collections.synchronizedList(list) 方法, 他会包装 list, 并对全部操做加锁

可是这种方式会 block 全部操做, 读, 写 都是串行的, 会影响效率

 

3. CopyOnWriteArrayList 如何解决这些问题

cowlist 的写操做全都加锁, 而且在加锁后会将底层数组复制一份再进行写操做, 当写操做完成之后, 整个替换底层数组

1) 使用锁, 即解决了并发写的问题

2) 读操做不加锁, 效率更高, 读写不冲突

3) 写操做使用副本控制, 解决读操做会读到 null 问题, 由于底层数据不会出现有空槽的中间状态

相关文章
相关标签/搜索