美文网首页
CopyOnWriteArrayList 源码分析

CopyOnWriteArrayList 源码分析

作者: bit_拳倾天下 | 来源:发表于2021-08-29 22:50 被阅读0次

众所周知,ArrayList 是线程不安全的。三个解决线程安全的方法:

  1. 使用 Collections.syncronized(arrayList) ,将集合同步化
  2. 使用 Vector 替代 ArrayList,其内部涉及到写的方法,都是 syncronized 修饰的同步方法,其他底层原理和 ArrayList 几乎相同。
  3. 使用 CopyOnWriteArrayList,在写方法的内部加锁,而不是直接在方法上加锁,会轻量级一点,然后增加写时复制逻辑,没有了容量一说。
    结论,第三种方法线程安全,效率相对较高,为最优解。可是为啥子?哎~先捡几个简单又能说明问题的方法看看源码再说。。。。
    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }
    /**
     * 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) {
        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();
        }
    }

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).  Returns the element that was removed from the list.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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();
        }
    }

    /**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
        return getArray().length;
    }

以这几个方法为例,可以看出,不论添加还是移除,集合内都会有一个新的数组 newElements,写操作是针对这个“复制”出来的数组进行的,写完之后,用 setArray 方法将这个“复制”并操作好的数组赋值给 array。所以称为“写时复制”。

内部没有想 ArrayList 那样单独维护一个 size 变量,调用 size() 方法,直接返回 array.length。增减元素,也不涉及 size 的加减了。每次增加元素都“复制”出一个长度为 array.length + 1 的数组,每次移除元素都“复制”一个长度为 array.length - 1 的数组。

这三种方案的线程安全,都指的是单个方法是线程安全的,比如多个线程单纯的用 add() 增加元素,可以保证线程安全。应注意的是,当多个方方组合判断时,仍会出现安全问题,比如:

if (copyOnWriteArrayList.size() == 0){
  copyOnWriteArrayList.add("添加元素");
}

如上,if 和 add 之间并没有保障,所以,不要以为用了这三种方案就万事大吉了,还是要注意安全问题!

CopyOnWriteArrayList 把写方法的锁放在方法内部,这确实比直接加在方法上好。三种方案中推荐使用的是 CopyOnWriteArrayList,但是写时复制,每次都涉及到数组的全量复制,不仅仅占用额外的内存,也造成额外的 CPU消耗。所以 CopyOnWriteArrayList 和 ArrayList 一样,不适合写操作多的场景。

另外,CopyOnWriteArrayList 不能保证实时性,他保证的是最终一致性,例如:a 线程在修改集合,但操作还没完,而 b 线程已经开始读取集合了,在这个时刻,两个线程的数据是不同的,也就是说 b 线程读到的是旧数据。但 a 线程操作完成后,b 再读数据就是新的了。所以,CopyOnWriteArrayList 保证的是最终一致性。

相关文章

网友评论

      本文标题:CopyOnWriteArrayList 源码分析

      本文链接:https://www.haomeiwen.com/subject/oynoiltx.html