欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

CopyOnWriteArrayList分析

程序员文章站 2022-07-14 16:10:05
...

ArrayList并没有考虑并发的情况,在多线程高并发访问ArrayList的情况下,它并不能保证线程安全。CopyOnWriteArrayList是ArrayList变种,而且它是线程安全的。CopyOnWriteArrayList的add(), remove(), set()等方法的实现都会创建基础数组的拷贝,并在新创建的数组上实现add, remove, set等修改操作,最后将新数组设置成基础数组。因此,旧的数组并没有被修改,其他线程对旧数组的访问依然是正常的,它不会受到新修改的影响。当然这是需要付出代价的,那就是每当修改容器时都会旧数组的同容全部拷贝一遍,这需要更多的内存,更多的读写数据时间开销,特别是当容器规模较大的时候。所以,CopyOnWriteArrayList适用于读操作远远多于写操作的场景,在这种情况下它能够提供更好的并发性能。另外,当你不能或者不想对遍历同步但又希望防止多线程并发的干扰时,CopyOnWriteArrayList就很有用了。

下面分析一下CopyOnWriteArrayList的主要方法:

 

get()方法

 

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

    public E get(int index) {
        return get(getArray(), index);
    }
 get()方法不需要加锁,它的实现也非常简单,只是调用了一个方法get(Object[] a, int index),获取基础数组index位置上的元素。

 

 

add()方法

 

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();
        }
    }

 这是带两个参数的add()方法,作用是在index的位置上插入一个element。由于这是修改操作,为了保证写操作是线程安全的,必须先获得锁。这里使用的是ReentrantLock的lock()方法获取锁。接着检查index的范围,如果超出了正常的范围,就抛出IndexOutOfBoundsException。如果index是指向最后一个元素,则将基础数组的所有元素复制到长度为len+1新数组;如果index指向中间的元素,则先把index前面的所有元素复制到新数组,空出新数组上index所指的位置用于容纳新元素,然后把index之后的所有元素复制到新数组。最后,在新数组的index位置添加element,将新数组设置为基础数组,释放锁。

 

CopyOnWriteArrayList的其他写操作set(), remove()等与add()方法的实现方式基本上是一致的,都需要获得ReentrantLock,复制底层数组到新数组,在新数组上进行修改,最后将新数组设为基础数组,释放锁。

 

CopyOnWriteArrayList内部提供了COWIterator。虽然它实现了ListIterator接口,但是它其他并不支持Iterator上的remove(), add()和set()操作,这些方法只是简单地抛出UnsupportedOperationException。COWIterator初始化的时候就获取了CopyOnWriteArrayList底层的基础数组,Iterator在遍历的过程中不支持对这个基础数组的修改,而其他线程在执行写操作的时候会将基础数组复制一遍,在新的基础数组上进行写操作。因此,无论其他线程执行哪种操作,COWIterator一完成初始化,它的基础数组拷贝就不会发生变化。