CopyOnWriteArrayList

list 是我们平时工作中应用最多的集合之一,但ArrayList是线程不安全的如果想使用线程安全的话可以使用Vector,这个类虽然是线程安全的,但效率比较低下,在JUC包中还有一个类可以代替Vector,那就是今天的主角:CopyOnWriteArrayList——写时复制,看名字应该可以猜到在做写时复制一份出来,那么就有可能会出现脏读的情况

基本机构

CopyOnWriteArrayList使用ReentrantLock控制并发锁的,使用array作为底层数据结构,默认构造函数会构造一个new Object[0]类型的数组

add(E e)

添加一个元素,涉及到写,那么就会使用到锁

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}
  • 使用ReentrantLock获取独占锁
  • 获取原数组以及原数组长度
  • 将原数组复制出一个新出去,长度是原数组长度+1,这里需要注意的是数组的复制使用的是浅克隆
  • 将新添加的元素放进新数组的尾部
  • 将新数组替换原来的数组并返回true
  • 释放锁

add(int index, E element)

在指定位置添加元素

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)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        Object[] newElements;
        int numMoved = len - index;
        if (numMoved == 0)
            newElements = Arrays.copyOf(elements, len + 1);
        else {
            newElements = new Object[len + 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
        newElements[index] = element;
        setArray(newElements);
    } finally {
        lock.unlock();
    }
}
  • 获取ReentrantLock锁
  • 获取原数组以及原数组长度
  • 如果指定的index大于了原数组长度或者index小于0抛出角标越界异常
  • 定义一个新数组newElements
  • 使用原数组长度减去index,如果为0,说明新添加的元素在数组尾部,逻辑和add(E e)的逻辑一样
  • 如果不为0,newElements的长度定义为len + 1
  • 将原数组赋值到新数组,从0开始复制,一直复制到index位置
  • 将原数组复制到新数组,从index+1位置开始复制,一直复制到len-index位置
  • 将新元素放置到新数组的index位置
  • 释放锁

get(int index)

获取指定下标的元素,这个方法调用的是get(Object[] a, int index)

public E get(int index) {
    return get(getArray(), index);
}

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

get方法很简单,首先获取原始数组,然后返回对应下标的元素即可。需要注意的是get方法没有加锁,那么就是弱一致性,可能会出现脏读。在介绍add方法时说了是复制一个数组进行新增的,那么可能就会出现读线程读的数据可能存在另一个写线程进行修改,那么读到的数据可能就是错误的

size()

获取长度,这个很简单:首先获取数组,然后返回数组的长度即可

set(int index, E element)

修改指定下标的元素,既然是修改,那么就要使用锁

public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);

        if (oldValue != element) {
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len);
            newElements[index] = element;
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            setArray(elements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}
  • 获取ReentrantLock锁并加锁
  • 获取原始数组并获取指定位置的oldValue
  • 判断oldValue是否和新的元素相等
    • 如果不等,那么获取原始数组的长度,将原始数组复制到一个新的数组中,修改新数组中指定下标的元素并替换原来的数组
    • 如果相等,直接使用老的数组替换老的数组
  • 返回oldValue
  • 释放锁

remove(int index)

删除指定下标的元素,既然是修改,就需要使用到锁

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)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            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();
    }
}
  • 获取ReentrantLock锁,并加锁
  • 获取原始数组并获取指定下标的元素oldValue
  • 需要需要移动的元素下标numMoved
  • 如果numMoved为0说明删除的元素是数组的最后一个元素,直接将老数组的0至len-1位置的元素替换老数组
  • 如果不为0,创建一个len-1长度的新数组newElements首先将老数组的0至index位置的元素放进newElements中;然后再将index+1至numMoved位置的元素放进newElements中,最后将newElements替换老数组
  • 返回oldValue
  • 释放锁
posted @ 2021-07-14 17:28  扭不动的奥利奥  阅读(90)  评论(0)    收藏  举报