Java集合003 --- CopyOnWriteArrayList

前言

CopyOnWriteArrayList实际上是ArrayList的线程安全版,内部实现也是数组,CopyOnWriteArrayList实现了RandomAccess、Cloneable、List、Serializable接口;

属性

// 可重入锁, 可重入的概念是: 当某个线程获取到锁之后, 该线程还能获取到该锁
// 不自动序列化
final transient ReentrantLock lock = new ReentrantLock();

// 数组, 不自动序列化、修改对其他线程可见并且禁止指令重排
private transient volatile Object[] array;

可以看到,CopyOnWriteArrayList与ArrayList的区别是:CopyOnWriteArrayList无数据实际个数size,这是为啥呢?

这是因为CopyOnWriteArrayList在写元素时,使用新的数组,然后赋值,因此数组的长度既是元素的个数

构造方法

final void setArray(Object[] a) {
    array = a;
}

public CopyOnWriteArrayList() {
    setArray(new Object[0]); // 创建容量为0的数组
}

public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    if (c.getClass() == CopyOnWriteArrayList.class)
        // 类型为CopyOnWriteArrayList, 强转然后获取数组
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
        // 和ArrayList类似, 存在元素类型不为Object的场景
        elements = c.toArray();
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    setArray(elements);
}

public CopyOnWriteArrayList(E[] toCopyIn) {
    // 直接使用数组
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

添加单个元素

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 加锁
    try {
        Object[] elements = getArray(); // 获取当前数组
        int len = elements.length;
        // 创建长度为len+1的新数组, 并且前len个元素为老数组的元素
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e; // 在新数组的最后添加元素
        setArray(newElements); // 更新list数组
        return true;
    } finally {
        lock.unlock(); // 释放锁
    }
}

// 指定位置添加元素
public void add(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 加锁
    try {
        Object[] elements = getArray(); // 获取当前数组
        int len = elements.length;
        if (index > len || index < 0) // index合法性检查
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        Object[] newElements;
        int numMoved = len - index;
        // 判断是不是在最后添加元素
        // 看了这几个list源码, 都是这样处理的;
        // 我写代码的思路是:所有的场景复用同一个流程, 看到此处源码, 感受颇多
        if (numMoved == 0)
            // 创建长度为len+1的新数组, 并且前len个元素为老数组的元素
            newElements = Arrays.copyOf(elements, len + 1);
        else {
            newElements = new Object[len + 1];
            // 先将原数组0~index-1个元素拷贝到新数组
            System.arraycopy(elements, 0, newElements, 0, index);
            // 将原数组index~end个元素拷贝到新数组index+1~end
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
        newElements[index] = element; // 将新数组的第index位置赋值
        setArray(newElements); // 更新数组
    } finally {
        lock.unlock(); // 释放锁
    }
}
public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock(); // 加锁
    try {
        Object[] elements = getArray(); // 获取原数组
        int len = elements.length;
        // 获取指定索引元素, 如果元素不存在, 抛ArrayIndexOutOfBoundsException
        // 而不是IndexOutOfBoundsException异常
        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];
            // 拷贝原数组0~index-1元素到新数组
            System.arraycopy(elements, 0, newElements, 0, index);
            // 拷贝原数组index+1~len元素到新数组
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);// 更新数组
        }
        return oldValue;// 返回index处的取值
    } finally {
        lock.unlock();//释放锁
    }
}

获取指定索引的元素

public E get(int index) {
    // 不校验index, 数组越界抛ArrayIndexOutOfBoundsException
    return get(getArray(), index);
}

@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
    return (E) a[index];
}

如果元素不存在则添加元素(返回值:元素存在返回false;元素不存在返回true)

public boolean addIfAbsent(E e) {
    Object[] snapshot = getArray();
    // 存在返回false; 不存在添加元素
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? 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]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        // 在最后添加元素
        Object[] newElements = Arrays.copyOf(current, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock(); // 释放锁
    }
}

对于集合来说,IfAbsent、IfPresent是很常见的方法,我在理解这块的功能的方法是:Absent是缺席的意思,AddIfAbsent的意思就是如果元素不存在则添加元素

序列化和反序列化

和ArrayList相似,都是采用按需序列化的方法

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException {

    s.defaultWriteObject();

    Object[] elements = getArray();
    // Write out array length
    s.writeInt(elements.length);

    // Write out all elements in the proper order.
    for (Object element : elements)
        s.writeObject(element);
}

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {

    s.defaultReadObject();

    // bind to new lock
    resetLock();

    // Read in array length and allocate array
    int len = s.readInt();
    Object[] elements = new Object[len];

    // Read in all elements in the proper order.
    for (int i = 0; i < len; i++)
        elements[i] = s.readObject();
    setArray(elements);
}

总结

1、CopyOnWriteArrayList内部由数组实现,线程安全

2、CopyOnWriteArrayList在写的时候,创建新的数组,写的时候内存占用高

3、CopyOnWriteArrayList适用读多写少的场景

4、CopyOnWriteArrayList能保证一致性,但是不能保证实时一致性

猜你喜欢

转载自www.cnblogs.com/sniffs/p/12926472.html