(6)并发编程高级篇-CopyOnWriteArrayList

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JavaMrZhang/article/details/87862899

介绍

java并发包中的并发List只有CopyOnWriteArrayList,CopyOnWriteArrayList的修改操作底层都是通过拷贝一份数组进行的。使用ReentrantLock独占锁来保证多线程并发下只有一个线程进行修改操作,下面我们通过源码分析来逐步了解。

初始化方法

   /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

无参构造器中,创建了一个长度为1Object数组.

添加元素

在这里插入图片描述
从上图可知CopyOnWriteArrayList的添加元素方法有7个,我们着重讲下add(E) 这个方法,其他方法的实现原理大同小异。


    /**
     * Appends the specified element to the end of this list.
     * 翻译:在列表的最后添加元素
     * @param e element to be appended to this list
     * 翻译:添加到列表的元素
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
         //1. 获取重入锁(独占锁)
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        //2. 获取array
            Object[] elements = getArray();
            int len = elements.length;
            //3. 复制原array到新array,并添加元素
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //4. 将新array替换原array
            setArray(newElements);
            return true;
        } finally {
           //5. 释放重入锁
            lock.unlock();
        }
    }

从1 和 5 可以看出 的方法,通过独占锁的机制,保证多线程并发修改元素时,只能有一个线程进行,其他线程会阻塞挂起,从而确保了此方法的原子性。
2 、3 处 通过复制原array到新array中,通过对新array进行添加元素操作,此处可知CopyOnWriteArrayList是一个无界的队列。4 处 将 新数组替换了原数组

删除元素

在这里插入图片描述
CopyOnWriteArrayList 有6个删除方法,我们着重讲解下remove(int)这个方法。

  /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left 
     *   Returns the element that was removed from the list.
     * 翻译:删除列表中指定位置的元素,往左边移动随后的元素,返回从列表中删除的元素。
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
    //1. 获取锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        //2. 获取原array
            Object[] elements = getArray();
            //3. 获取要删除的元素
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;

            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
            //4. 通过两次复制,来删除元素并整合新的array
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
              //5. 新array替换原array
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

获取指定位置元素

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

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }

在如上代码中,当线程threadA 执行get(int index ),是分两个步骤:1. 获取数组 2. 通过数组下标index索引获取指定元素,它会存在数据弱一致性的问题。当线程threadB 在 线程 threadA执行步骤1后和步骤2之前时,删除了threadA 要访问的元素(假设为x元素),那么threadA执行步骤2时获取的是x元素,而不是其他的。因为threadA在执行步骤1后,操作的是原数组(局部变量)。

总结

CopyOnWriteArrayList通过写时复制来保证数据的一致性,而它获取、修改、写入这三个操作并不是原子性,所以通过独占锁来保证任何一时刻只能有一个线程对数据进行操作。另外CopyOnWriteArraySet的实现原理和CopyOnWriteArrayList也是类似的。

猜你喜欢

转载自blog.csdn.net/JavaMrZhang/article/details/87862899
今日推荐