JUC

CopyOnWriteList源码分析

2019-11-08  本文已影响0人  秋名山车神_f776
CopyOnWriteArrayList 功能简介
CopyOnWriteArrayList 是juc中提供的 并发安全的 ArrayList, 我们拆分一下类名 "Copy" "On" "Write" "ArrayList", 从字面意思我们推断出, 这个是以在 Write 时进行 Copy 数组元素的 ArrayList.
它主要具有一下特性:

所有元素都存储在数组里面, 只有当数组进行 remove, update时才在方法上加上 ReentrantLock , 拷贝一份 snapshot 的数组, 只改变 snapshot 中的元素, 最后再赋值到 CopyOnWriteArrayList 中
所有的 get方法只是获取数组对应下标上的元素(无需加锁控制)
从上面两个特性我们也知道: CopyOnWriteArrayList 是使用空间换时间的方式进行工作, 它主要适用于 读多些少, 并且数据内容变化比较少的场景(最好初始化时就进行加载数据到CopyOnWriteArrayList 中)

元素添加 add 方法
直接添加元素 e 到数组的尾部

/**

}

添加元素到数组的指定位置

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the lement currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices)
 */
@Override
public void add(int index, E element) {
    /**
     * 将元素 e 插入到数组 指定的索引下标 index 下
     * 操作步骤:
     *      1. 获取全局的锁
     *      2. 获取 CopyOnWriteArrayList 的 array, 及 array.length
     *      3. 进行参数校验 (index > len || index < 0) 则直接抛异常 -> 说明元素的插入只能在 0 - array.length 之间(包含两个端点)
     *      4. 获取插入点 index 与 array.length 之间的步长, 进行分类讨论
     *          1) 插入的数据正好在 原array数组的后一个节点 (numMoved = len), 则直接新建一个 array, 将原来的 array copy 过来
     *          2) 插入的 index 满足 0 <= index <= len - 1, 则新建一个数组, 原来 o -> index(index不包含) 拷贝来, index后面的数据拷贝到新数组的 index + 1 的空间
     *      5. 将 e 设置到 新 array 的 index 位置
     *      6. 将 新 array 设置到 CopyOnWriteArrayList 里面
     */
    final ReentrantLock lock = this.lock;
    lock.lock();                                                                    // 1. 获取全局的锁
    try{
        Object[] elements = getArray();
        int len = elements.length;
        if(index > len || index < 0){
            throw new IndexOutOfBoundsException("Index: " + index + ", Size:" + len);
        }
        Object[] newElements;
        int numMoved = len - index;
        if(numMoved == 0){ // 走到这一步, 说明 数据是插入到 oldArray.length(这个值是指下标) 位置上的元素
            newElements = Arrays.copyOf(elements, len + 1); // 直接拷贝原数组到一个新的 array 数组中, 这个数组的长度是 len + 1
        }else{
            newElements = new Object[len + 1];
            System.arraycopy(elements, 0, newElements, 0, index); // 将原数组 index 前的数组都拷贝到新的数组里面
            System.arraycopy(elements, index, newElements, index + 1, numMoved); // 将原数组 index 以后的元素都 copy到新的数组里面(包括index位置的元素)
        }
        newElements[index] = element; // 将 index 赋值 element
        setArray(newElements); // 将 新的 array set到 CopyOnWriteArrayList 上
    }finally {
        lock.unlock();
    }

}

将元素 e 插入到数组 指定的索引下标 index 下 (整个操作比较简单)
操作步骤:

获取全局的锁
获取 CopyOnWriteArrayList 的 array, 及 array.length
进行参数校验 (index > len || index < 0) 则直接抛异常 -> 说明元素的插入只能在 0 - array.length 之间(包含两个端点)
获取插入点 index 与 array.length 之间的步长, 进行分类讨论
插入的数据正好在 原array数组的后一个节点 (numMoved = len), 则直接新建一个 array, 将原来的 array copy 过来
插入的 index 满足 0 <= index <= len - 1, 则新建一个数组, 原来 o -> index(index不包含) 拷贝来, index后面的数据拷贝到新数组的 index + 1 的空间
将 e 设置到 新 array 的 index 位置
将 新 array 设置到 CopyOnWriteArrayList 里面

元素删除 remove 方法
删除指定索引 index 上的元素

/**

直接删除元素 e

public boolean remove(Object o){
Object[] snapshot = getArray();
// 获取 index 在 snapshot 中的位置, -1 表示不存在
int index = indexOf(o, snapshot, 0, snapshot.length);
return (index < 0) ? false : remove(o, snapshot, index);
}

/**

代码稍微多一点, 主要树为了再第一步获取 数组 与第二步操作之间因并发导致 snapshot 与原数组元素不一致, 而做了修复的操作;
数据不一致主要通过 snapshot != current 进行判断
修复操作主要分 下面 4 中情况:

从 index,len 中取出一个较小的值 prefix, 从 current的prefix前个元素中寻找元素 o, 找到后, 直接 break, 执行下面的操作
若 index >= len, 则说明 元素 o 在另外的线程中已经被删除, 直接 return
current[index] = o, 则说明, index 位置上的元素 o 还在那边, 直接 break
最后 在 index 与 len 之间寻找元素, 找到位置直接接下来的代码, 没找到 直接 return

元素替换 set 方法

/**

set方法比较简单, 一般直接看代码就 OK 了;

总结
CopyOnWriteArrayList 是使用空间换时间的方式进行工作
它主要适用于 读多些少, 并且数据内容变化比较少的场景(最好初始化时就进行加载数据到CopyOnWriteArrayList 中)
上一篇下一篇

猜你喜欢

热点阅读