Java八股文系列二:集合之CopyOnWriteArrayList

344 阅读2分钟

image

一、CopyOnWriteArrayList源码

1.1 成员变量

    //可重入锁
    final transient ReentrantLock lock = new ReentrantLock();

    //对象数组,注意用到volatile修饰
    private transient volatile Object[] array;

1.2 构造方法

    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * 使用指定 Collection 来构造 CopyOnWriteArrayList
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            if (c.getClass() != ArrayList.class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

    /**
     * 复制一份给定的数组
     */
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

1.3 add操作

CopyOnWriteArrayList的add

    //CopyOnWriteArrayList的add
    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();
        }
    }

加锁,直接复制原有的数组,新元素加入新数组,将原来的数组指针指向新的数组。

ArrayList的add

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

1.4 get操作

CopyOnWriteArrayList的get

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

没有加锁。

ArrayList的get

    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

1.5 remove操作

CopyOnWriteArrayList的remove

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

加锁,对除了要删除的元素进行复制,将原数组的指针指向新的数组。

ArrayList的remove

    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

1.6 小结

  • CopyOnWriteArrayList的读方法和ArrayList的读方法没有区别,都没加锁。
  • CopyOnWriteArrayList的写方法都加了可重入锁,都是对原数组进行复制并做相应的修改,然后将数组指针指向新的数组。
  • 写写阻塞,读写不阻塞。

二、CopyOnWriteArrayList常见面试问题

2.1 CopyOnWriteArrayList和ArrayList的区别

CopyOnWriteArrayList是线程安全的,通过加可重入锁实现,比如两个线程同时add操作不会出现一个线程覆盖掉另一个线程add的值;ArrayList线程不安全。

2.2 CopyOnWriteArrayList的优缺点

  • 线程安全。
  • 写操作时完全拷贝数组,消耗内存。
  • 如果写操作还没完成,那么多操作会读到旧值。读操作有延迟。

三、总结

虽然CopyOnWriteArrayList是线程安全的,但是存在读滞后于写的情况,所以适用于读多写少的并发场景。写操作需要复制数组,消耗内存严重。