并发包中的并发List只有CopyOnWriteArrayList。

实现原理:利用写时复制策略,在底层进行修改操作的时候都是通过复制的数组(快照)上进行。

J.U.C之CopyOnWriteArrayList

如何控制并发安全:
上述类图中,每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,ReentrantLock独占锁对象用来保证同时只有一个线程对array修改。

如何自己实现一个写时复制的线程安全的list,需要注意哪些点?

何时初始化list, 初始化list元素个数以及是否有限,动态扩容之类的(与arrayList一样)
如何保证线程安全,比如多个线程进行读写时候如何保证线程安全的(多线程读写)
如何保证使用迭代器遍历list的数据一致性(遍历数据一致性)

源码—初始化
J.U.C之CopyOnWriteArrayList
源码—添加元素
J.U.C之CopyOnWriteArrayList
源码—获取指定元素

  // 位置访问操作

    /**
     * {@inheritDoc}
     *元素不存在抛出数组下标越界异常
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }
    第一步
      final Object[] getArray() {
        return array;
    }
    第二步
        @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

获取下标方法操作的时候,第一步先获取数组对象,第二步通过下标访问指定的元素。但是整个过程没有加锁同步。这里就会出现写时候复制策略的弱一致性问题。
J.U.C之CopyOnWriteArrayList
set方法

/**
   替换指定的集合中的元素
     *
     * 抛出数组越界的异常如果不存在
     */
    public E set(int index, E element) {
    //获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
        //获取数组并调用get方法获取当前位置的数组元素
            Object[] elements = getArray();
            E oldValue = get(elements, index);
            //如果指定的元素与之前的元素值不一样,则copy一份新的数组并将该元素添加到指定的位置
            if (oldValue != element) {
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            } else {
                //如果指定位置元素与新值一样,则为了保证volatile语义,还是需要重新设置数组
                setArray(elements);
            }
            //返回指定位置的原值
            return oldValue;
        } finally {
        //释放独占锁
            lock.unlock();
        }
    }

删除指定元素

  /**
     * 移除list中指定的元素
    将其后面的元素统一向左移动
      返回被移除的值
     *
     * 没有则抛出 IndexOutOfBoundsException 异常
     */
    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 {
            如果不是最后一个元素就copy两个数组
                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();
        }
    }

小结:
通过写时复制策略来保证list的一致性,而获取-修改-写入操作并不是原子性的,所以在增删改的过程中使用了独占锁,来保证同一个时刻,只有一个线程能对list数组进行修改。但是也正因为如此,迭代器遍历的时候会出现弱一致性问题(使用时候需要特别注意)

相关文章: