CopyOnWriteArrayList源码

news2025/1/16 14:02:22

介绍

CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略

  1. 在保证并发读取的前提下,确保了写入时的线程安全;
  2. 由于每次写入操作时,进行了Copy复制原数组,所以无需扩容;
  3. 适合读多写少的应用场景。由于add()、set() 、 remove()等修改操作需要复制整个数组,所以会有内存开销大的问题。
  4. CopyOnWriteArrayList由于只在写入时加锁,所以只能保证数据的最终一致性,不能保证数据的实时一致性。
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable

image-20230623205955598

常量&变量

    //序列化版本号
    private static final long serialVersionUID = 8673264195747942595L;

    /** The lock protecting all mutators */
    //控制所有函数的同步锁
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    //存储数据的数组,设置volatile,保证可见性
    private transient volatile Object[] array;

构造方法

    /**
     * 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;
    }

    /**
     * Creates an empty list.
     * 创建一个空的CopyOnWriteArrayList对象。
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * Creates a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection of initially held elements
     * @throws NullPointerException if the specified collection is null
     *创建一个包含给定集合元素的CopyOnWriteArrayList对象。
     *集合c中的元素按照它们在集合中的顺序被添加到新的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);
    }

    /**
     * Creates a list holding a copy of the given array.
     *
     * @param toCopyIn the array (a copy of this array is used as the
     *        internal array)
     * @throws NullPointerException if the specified array is null
     *创建一个包含给定数组元素的CopyOnWriteArrayList对象。
     *数组toCopyIn中的元素按照它们在数组中的顺序被添加到新的CopyOnWriteArrayList对象中。
     */
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

内部类

COWIterator

COWIterator表示迭代器,其也有一个Object类型的数组作为CopyOnWriteArrayList数组的快照,这种快照风格的迭代器方法在创建迭代器时使用了对当时数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。

    static final class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */
        //快照
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        //游标
        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }
        //是否有下一个元素
        public boolean hasNext() {
            return cursor < snapshot.length;
        }
        //是否有上一个元素
        public boolean hasPrevious() {
            return cursor > 0;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            if (! hasPrevious())
                throw new NoSuchElementException();
            return (E) snapshot[--cursor];
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor-1;
        }

        /**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code remove}
         *         is not supported by this iterator.
         *         不支持remove
         */
        public void remove() {
            throw new UnsupportedOperationException();
        }

        /**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code set}
         *         is not supported by this iterator.
         *         不支持set
         */
        public void set(E e) {
            throw new UnsupportedOperationException();
        }

        /**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code add}
         *         is not supported by this iterator.
         *         不支持add
         */
        public void add(E e) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            Object[] elements = snapshot;
            final int size = elements.length;
            for (int i = cursor; i < size; i++) {
                @SuppressWarnings("unchecked") E e = (E) elements[i];
                action.accept(e);
            }
            cursor = size;
        }
    }

COWSubList

CopyOnWriteArrayList定义了一个子类COWSubList,用于在sublist时返回,为了少写代码,此类继承了AbstractList,它有以下特点:

没有使用modCount来记录是否有其他修改,而是通过一种更巧妙的方式——它记录了对当前数组的引用,如果当前数组的引用和CopyOnWriteArrayList.getArray()结果不同,表示有人创建了新的副本并进行了修改操作

此类没有专门做优化,所以addAll()之类的批量操作方法是通过批量调用add()实现的,增加10个值可能会连续创建10个副本,这里可能有性能问题,

    /**
     * Sublist for CopyOnWriteArrayList.
     * This class extends AbstractList merely for convenience, to
     * avoid having to define addAll, etc. This doesn't hurt, but
     * is wasteful.  This class does not need or use modCount
     * mechanics in AbstractList, but does need to check for
     * concurrent modification using similar mechanics.  On each
     * operation, the array that we expect the backing list to use
     * is checked and updated.  Since we do this for all of the
     * base operations invoked by those defined in AbstractList,
     * all is well.  While inefficient, this is not worth
     * improving.  The kinds of list operations inherited from
     * AbstractList are already so slow on COW sublists that
     * adding a bit more space/time doesn't seem even noticeable.
     */
    private static class COWSubList<E>
        extends AbstractList<E>
        implements RandomAccess
    {
        private final CopyOnWriteArrayList<E> l;
        private final int offset;
        private int size;
        private Object[] expectedArray;

        // only call this holding l's lock
        COWSubList(CopyOnWriteArrayList<E> list,
                   int fromIndex, int toIndex) {
            l = list;
            expectedArray = l.getArray();
            offset = fromIndex;
            size = toIndex - fromIndex;
        }

        // only call this holding l's lock
        private void checkForComodification() {
            if (l.getArray() != expectedArray)
                throw new ConcurrentModificationException();
        }

        // only call this holding l's lock
        private void rangeCheck(int index) {
            if (index < 0 || index >= size)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ",Size: "+size);
        }

        public E set(int index, E element) {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                rangeCheck(index);
                checkForComodification();
                E x = l.set(index+offset, element);
                expectedArray = l.getArray();
                return x;
            } finally {
                lock.unlock();
            }
        }

        public E get(int index) {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                rangeCheck(index);
                checkForComodification();
                return l.get(index+offset);
            } finally {
                lock.unlock();
            }
        }

        public int size() {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                checkForComodification();
                return size;
            } finally {
                lock.unlock();
            }
        }

        public void add(int index, E element) {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                checkForComodification();
                if (index < 0 || index > size)
                    throw new IndexOutOfBoundsException();
                l.add(index+offset, element);
                expectedArray = l.getArray();
                size++;
            } finally {
                lock.unlock();
            }
        }

        public void clear() {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                checkForComodification();
                l.removeRange(offset, offset+size);
                expectedArray = l.getArray();
                size = 0;
            } finally {
                lock.unlock();
            }
        }

        public E remove(int index) {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                rangeCheck(index);
                checkForComodification();
                E result = l.remove(index+offset);
                expectedArray = l.getArray();
                size--;
                return result;
            } finally {
                lock.unlock();
            }
        }

        public boolean remove(Object o) {
            int index = indexOf(o);
            if (index == -1)
                return false;
            remove(index);
            return true;
        }

        public Iterator<E> iterator() {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                checkForComodification();
                return new COWSubListIterator<E>(l, 0, offset, size);
            } finally {
                lock.unlock();
            }
        }

        public ListIterator<E> listIterator(int index) {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                checkForComodification();
                if (index < 0 || index > size)
                    throw new IndexOutOfBoundsException("Index: "+index+
                                                        ", Size: "+size);
                return new COWSubListIterator<E>(l, index, offset, size);
            } finally {
                lock.unlock();
            }
        }

        public List<E> subList(int fromIndex, int toIndex) {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                checkForComodification();
                if (fromIndex < 0 || toIndex > size || fromIndex > toIndex)
                    throw new IndexOutOfBoundsException();
                return new COWSubList<E>(l, fromIndex + offset,
                                         toIndex + offset);
            } finally {
                lock.unlock();
            }
        }

        public void forEach(Consumer<? super E> action) {
            if (action == null) throw new NullPointerException();
            int lo = offset;
            int hi = offset + size;
            Object[] a = expectedArray;
            if (l.getArray() != a)
                throw new ConcurrentModificationException();
            if (lo < 0 || hi > a.length)
                throw new IndexOutOfBoundsException();
            for (int i = lo; i < hi; ++i) {
                @SuppressWarnings("unchecked") E e = (E) a[i];
                action.accept(e);
            }
        }

        public void replaceAll(UnaryOperator<E> operator) {
            if (operator == null) throw new NullPointerException();
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                int lo = offset;
                int hi = offset + size;
                Object[] elements = expectedArray;
                if (l.getArray() != elements)
                    throw new ConcurrentModificationException();
                int len = elements.length;
                if (lo < 0 || hi > len)
                    throw new IndexOutOfBoundsException();
                Object[] newElements = Arrays.copyOf(elements, len);
                for (int i = lo; i < hi; ++i) {
                    @SuppressWarnings("unchecked") E e = (E) elements[i];
                    newElements[i] = operator.apply(e);
                }
                l.setArray(expectedArray = newElements);
            } finally {
                lock.unlock();
            }
        }

        public void sort(Comparator<? super E> c) {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                int lo = offset;
                int hi = offset + size;
                Object[] elements = expectedArray;
                if (l.getArray() != elements)
                    throw new ConcurrentModificationException();
                int len = elements.length;
                if (lo < 0 || hi > len)
                    throw new IndexOutOfBoundsException();
                Object[] newElements = Arrays.copyOf(elements, len);
                @SuppressWarnings("unchecked") E[] es = (E[])newElements;
                Arrays.sort(es, lo, hi, c);
                l.setArray(expectedArray = newElements);
            } finally {
                lock.unlock();
            }
        }

        public boolean removeAll(Collection<?> c) {
            if (c == null) throw new NullPointerException();
            boolean removed = false;
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                int n = size;
                if (n > 0) {
                    int lo = offset;
                    int hi = offset + n;
                    Object[] elements = expectedArray;
                    if (l.getArray() != elements)
                        throw new ConcurrentModificationException();
                    int len = elements.length;
                    if (lo < 0 || hi > len)
                        throw new IndexOutOfBoundsException();
                    int newSize = 0;
                    Object[] temp = new Object[n];
                    for (int i = lo; i < hi; ++i) {
                        Object element = elements[i];
                        if (!c.contains(element))
                            temp[newSize++] = element;
                    }
                    if (newSize != n) {
                        Object[] newElements = new Object[len - n + newSize];
                        System.arraycopy(elements, 0, newElements, 0, lo);
                        System.arraycopy(temp, 0, newElements, lo, newSize);
                        System.arraycopy(elements, hi, newElements,
                                         lo + newSize, len - hi);
                        size = newSize;
                        removed = true;
                        l.setArray(expectedArray = newElements);
                    }
                }
            } finally {
                lock.unlock();
            }
            return removed;
        }

        public boolean retainAll(Collection<?> c) {
            if (c == null) throw new NullPointerException();
            boolean removed = false;
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                int n = size;
                if (n > 0) {
                    int lo = offset;
                    int hi = offset + n;
                    Object[] elements = expectedArray;
                    if (l.getArray() != elements)
                        throw new ConcurrentModificationException();
                    int len = elements.length;
                    if (lo < 0 || hi > len)
                        throw new IndexOutOfBoundsException();
                    int newSize = 0;
                    Object[] temp = new Object[n];
                    for (int i = lo; i < hi; ++i) {
                        Object element = elements[i];
                        if (c.contains(element))
                            temp[newSize++] = element;
                    }
                    if (newSize != n) {
                        Object[] newElements = new Object[len - n + newSize];
                        System.arraycopy(elements, 0, newElements, 0, lo);
                        System.arraycopy(temp, 0, newElements, lo, newSize);
                        System.arraycopy(elements, hi, newElements,
                                         lo + newSize, len - hi);
                        size = newSize;
                        removed = true;
                        l.setArray(expectedArray = newElements);
                    }
                }
            } finally {
                lock.unlock();
            }
            return removed;
        }

        public boolean removeIf(Predicate<? super E> filter) {
            if (filter == null) throw new NullPointerException();
            boolean removed = false;
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                int n = size;
                if (n > 0) {
                    int lo = offset;
                    int hi = offset + n;
                    Object[] elements = expectedArray;
                    if (l.getArray() != elements)
                        throw new ConcurrentModificationException();
                    int len = elements.length;
                    if (lo < 0 || hi > len)
                        throw new IndexOutOfBoundsException();
                    int newSize = 0;
                    Object[] temp = new Object[n];
                    for (int i = lo; i < hi; ++i) {
                        @SuppressWarnings("unchecked") E e = (E) elements[i];
                        if (!filter.test(e))
                            temp[newSize++] = e;
                    }
                    if (newSize != n) {
                        Object[] newElements = new Object[len - n + newSize];
                        System.arraycopy(elements, 0, newElements, 0, lo);
                        System.arraycopy(temp, 0, newElements, lo, newSize);
                        System.arraycopy(elements, hi, newElements,
                                         lo + newSize, len - hi);
                        size = newSize;
                        removed = true;
                        l.setArray(expectedArray = newElements);
                    }
                }
            } finally {
                lock.unlock();
            }
            return removed;
        }

        public Spliterator<E> spliterator() {
            int lo = offset;
            int hi = offset + size;
            Object[] a = expectedArray;
            if (l.getArray() != a)
                throw new ConcurrentModificationException();
            if (lo < 0 || hi > a.length)
                throw new IndexOutOfBoundsException();
            return Spliterators.spliterator
                (a, lo, hi, Spliterator.IMMUTABLE | Spliterator.ORDERED);
        }

    }

COWSubListIterator

CopyOnWriteArrayList为内部类COWSubList提供了一个用于遍历的子类COWSubListIterator,一个标准实现而已,没啥特别的。

  private static class COWSubListIterator<E> implements ListIterator<E> {
        private final ListIterator<E> it;
        private final int offset;
        private final int size;

        COWSubListIterator(List<E> l, int index, int offset, int size) {
            this.offset = offset;
            this.size = size;
            it = l.listIterator(index+offset);
        }

        public boolean hasNext() {
            return nextIndex() < size;
        }

        public E next() {
            if (hasNext())
                return it.next();
            else
                throw new NoSuchElementException();
        }

        public boolean hasPrevious() {
            return previousIndex() >= 0;
        }

        public E previous() {
            if (hasPrevious())
                return it.previous();
            else
                throw new NoSuchElementException();
        }

        public int nextIndex() {
            return it.nextIndex() - offset;
        }

        public int previousIndex() {
            return it.previousIndex() - offset;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public void set(E e) {
            throw new UnsupportedOperationException();
        }

        public void add(E e) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            int s = size;
            ListIterator<E> i = it;
            while (nextIndex() < s) {
                action.accept(i.next());
            }
        }
    }

常用方法

get

获取指定索引位置的元素

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

getArray方法用于获取当前的快照数组,get方法返回快照数组中指定索引位置的元素

add

将指定元素添加到此列表的尾部

1获取锁,加锁后,获取当前数组,获取当前数组的长度

2根据当前数组赋值出一个长度为len+1的新数组newElements,此时newElements最后一个元素为null

3将元素e添加到newElements数组尾部,再设置当前Object[]为newElements,最终释放锁

    /**
     * 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})
     *  向list中添加元素
     */
    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);
            // 将新数组的最后一个元素设置为e
            newElements[len] = e;
            // 设置数组
            setArray(newElements);
            return true;
        } finally {
            // 解锁
            lock.unlock();
        }
    }

addIfAbsent

添加一个元素到列表中,但仅当列表中没有相同的元素存在时。如果列表中已经存在相同的元素,则此方法不会添加该元素并返回false

addIfAbsent(E e) 此方法首先获取当前列表的快照,然后检查该元素是否已经存在于列表中。如果不存在,则使用锁保护的addIfAbsent方法将该元素添加到列表中。

    /**
     * Appends the element, if not present.
     *
     * @param e element to be added to this list, if absent
     * @return {@code true} if the element was added
     * 添加元素,如果数组中不存在,添加,存在,则不添加
     */
    public boolean addIfAbsent(E e) {
        //获取当前数组
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }

addIfAbsent(E e, Object[] snapshot)这个方法首先检查快照是否与当前列表相同,如果不同则必须再次检查该元素是否已经存在于列表中。最后,如果该元素不存在于列表中,则将其添加到列表中。如果该元素已经存在于列表中,则不会进行添加操作并返回false。

    /**
     * A version of addIfAbsent using the strong hint that given
     * recent snapshot does not contain e.
     */
    private boolean addIfAbsent(E e, Object[] snapshot) {
        //重入锁
        final ReentrantLock lock = this.lock;
        //获取锁
        lock.lock();
        try {
            //获取当前数组
            Object[] current = getArray();
            //数组长度len
            int len = current.length;
            //快照不等于当前数组,对数组进行了修改
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                //二者取最小
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    //当前数组的元素与快照的元素不相等并且e与当前元素相等
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        //表示在snapshot与current之间修改了数组,并且设置了数组某一元素为e,已经存在
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                    //在数组中找到了元素e
                        return false;
            }
            //赋值数组
            Object[] newElements = Arrays.copyOf(current, len + 1);
            //索引len的元素赋值为e
            newElements[len] = e;
            //将新数组设置为当前数组
            setArray(newElements);
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }

set

替换指定位置的元素

    /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * 更新指定下标的元素
     */
    public E set(int index, E element) {
        // 获取公共锁
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
            // 获取数组
            Object[] elements = getArray();
            // 获取数组插入位置的元素
            E oldValue = get(elements, index);
            // 当值不相等时进行更新
            if (oldValue != element) {
            //数组长度
                int len = elements.length;
                // 拷贝数组
                Object[] newElements = Arrays.copyOf(elements, len);
                //重新赋值
                newElements[index] = element;
                // 设置数组
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                // 当值相同时,设置数组
                setArray(elements);
            }
            // 返回原来的值
            return oldValue;
        } finally {
            // 解锁
            lock.unlock();
        }
    }

set方法的实现非常简单:先复制一份数组,然后在新数组上进行替换操作,最后通过CAS操作将原数组替换为新数组即可。由于CopyOnWriteArrayList是线程安全的,所以操作过程中不需要加锁。

remove

删除指定下标的元素

1.获取锁,获取数组elements,数组长度为length,获取索引的值elements[index] (oldValue),计算需要移动的元素个数(length - index - 1),若个数为0,则表示移除的是数组的最后一个元素,复制elements数组,复制长度为length-1,然后设置数组进入步骤3;否则,进入步骤2。

2.先复制index索引前的元素,再复制index索引后的元素,然后设置数组。

3.释放锁,返回旧值。

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

总结

优点

读操作性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。

Java的list在遍历时,若中途有别的线程对list容器进行修改,则会抛ConcurrentModificationException异常。而CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了

缺点

内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC;

无法保证实时性,Vector对于读写操作均加锁同步,可以保证读和写的强一致性。而CopyOnWriteArrayList由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/678667.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百度 RT-DETR : 在实时目标检测上击败所有 YOLO !

论文地址&#xff1a;https://arxiv.org/abs/2304.08069 代码地址&#xff1a;https://github.com/PaddlePaddle/PaddleDetection 最近&#xff0c;基于端到端的 Transformer 检测器&#xff08;DETRs&#xff09;取得了显著的性能。然而&#xff0c;DETRs 的高计算成本问题尚…

chatgpt赋能python:Python的加减乘除使用指南

Python的加减乘除使用指南 Python是一种高级编程语言&#xff0c;其数学计算库和处理数值数据的能力使其成为科学计算&#xff0c;数据分析和机器学习领域的首选语言之一。在Python中&#xff0c;加减乘除是最基本和常用的四种数学计算操作。在本篇文章中&#xff0c;我们将介…

pyqt的学习(三)----鼠标点击和按键处理

QmyChartView 类的说明 QChart 和 QChartView 是基于 Graphics View 结构的绘图类。要对一个 QChart 图表进行鼠 标和按键操作&#xff0c;需要在 QChartView 类里对鼠标和按键事件进行处理&#xff0c;这就需要自定义一个 从 QChartView 继承的类。 QmyChartView 类是从 QC…

津津乐道设计模式 - 策略模式详解(以女友购物策略让你彻底明白)

&#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Micro麦可乐的博客 &#x1f425;《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程&#xff0c;入门到实战 &#x1f33a;《RabbitMQ》…

Git工具【系统学习】

第一章 Git快速入门 1.1 Git概述 Git是一个免费的&#xff0c;开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型或大型的各种项目。Git易于学习&#xff0c;占用空间小&#xff0c;性能快得惊人。 1.2 SCM概述 SCM&#xff08;Software Configuration Managem…

Deepin Community Live CD New Kernel——自带6.3.8内核的镜像和apt源

镜像介绍 此镜像属于 Deepin Community Live CD 系列&#xff08;Deepin Community Live CD 简称为 DCLC&#xff0c;Deepin Community Live CD 是什么&#xff1f;传送门&#xff1a;https://bbs.deepin.org/post/242933&#xff09;&#xff0c;New Kernel 系列镜像旨在可以…

chatgpt赋能python:Python的下载流程

Python的下载流程 Python是一款流行的编程语言&#xff0c;其掌握程度在当今计算机科学领域越来越受到重视。在本文中&#xff0c;我们将探讨Python的下载流程&#xff0c;为初学者提供一些有用的信息。特别是&#xff0c;我们将着重介绍如何在Windows操作系统上下载Python。 …

Golang每日一练(leetDay0105) 超级丑数、右侧小于当前元素的个数

目录 313. 超级丑数 Super Ugly Number &#x1f31f;&#x1f31f; 315. 计算右侧小于当前元素的个数 Count-of-smaller-numbers-after-self &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练…

chatgpt赋能python:Python下载安装教程

Python下载安装教程 Python是一种高级编程语言&#xff0c;具有简单易学、强大多样的特点&#xff0c;不仅可以用来开发网站、应用程序、游戏&#xff0c;还可以用于数据科学、机器学习、人工智能等领域&#xff0c;被广泛应用于各行各业。 本文将为您提供详细的Python下载安…

MongoDB简介

目录 1、NoSQL概述 2、什么是MongoDB 3、MongoDB特点 一、MongoDB安装&#xff08;docker方式&#xff09; 二、MongoDB安装&#xff08;普通方式&#xff09; 三、MongoDB 概念解析 1、NoSQL概述 NoSQL(NoSQL Not Only SQL)&#xff0c;意即反SQL运动&#xff0c;指的是…

FcaNet: Frequency Channel Attention Networks论文总结

论文&#xff1a;https://arxiv.org/abs/2012.11879 中文版&#xff1a;FcaNet: Frequency Channel Attention Networks 源码&#xff1a;https://github.com/cfzd/FcaNet或https://gitee.com/yasuo_hao/FcaNet 一、论文背景和出发点 问题&#xff1a;许多工作都集中在如何设计…

【T+】安装畅捷通T+提示安装向导找不到环境检测工具,是否手动选择环境检测工具文件夹。

【问题描述】 在windows server 2008r2系统环境下&#xff0c; 安装畅捷通T专属云标准版18.0软件的时候&#xff0c;提示&#xff1a; 安装向导找不到环境检测工具&#xff0c;是否手动选择环境检测工具文件夹&#xff08;CheckEnvironment&#xff09; 点击【是】手动选择&…

leetcode数据库题第六弹

leetcode数据库题第六弹 626. 换座位1280. 学生们参加各科测试的次数1321. 餐馆营业额变化增长1327. 列出指定时间段内所有的下单产品1341. 电影评分1378. 使用唯一标识码替换员工ID1393. 股票的资本损益1407. 排名靠前的旅行者1484. 按日期分组销售产品1517. 查找拥有有效邮箱…

数字基带传输

常用码型&#xff1a; 为了适应信道的传输&#xff0c;传输码型必须具备以下基本特性&#xff1a; 1&#xff09;无直流、很少的低频分量&#xff1b; 2&#xff09;含有码元定时信息&#xff1b; 3&#xff09;主瓣宽度窄&#xff1b; 4&#xff09;适用于各种信源的…

ChatGPT从入门到精通,一站式掌握办公自动化/爬虫/数据分析和可视

课程名称适应人群ChatGPT从入门到精通&#xff0c;一站式掌握办公自动化/爬虫/数据分析和可视 全面AI时代就在转角&#xff0c;道路已经铺好了“局外人”or“先行者”就在此刻等你决定 1、对ChatGPT感兴趣并希望有人手把手教学的新手 2、希望了解GPT各类应用抓住未来风口 3、希…

【软件设计师暴击考点】UML知识高频考点暴击系列

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;软件…

【Android Framework系列】第2章 Binder机制大全

1 Binder简介 1.1 什么是Binder Binder是Android中主要的跨进程通信方式。Android系统中&#xff0c;每个应用程序是由Android的Activity&#xff0c;Service&#xff0c;BroadCast&#xff0c;ContentProvider这四剑客中一个或多个组合而成&#xff0c;这四剑客所涉及的多进程…

【瑞萨RA_FSP】IWDT——独立看门狗定时器

文章目录 一、IWDT简介二、IWDT功能框图剖析1. IWDT 时钟源(1) 计数器时钟(2) 独立看门狗超时时间计算 2. IWDT 模块电路功能讲解3. 独立看门狗&#xff08;IWDT&#xff09;与看门狗&#xff08;WDT&#xff09;功能对比4. 怎么使用IWDT 三、IWDT实验1. 硬件设计2. 文件结构3.…

交通 | 考虑供需交互下的航空网络优化问题

编者按&#xff1a; 本文提出了一种包含供需交互作用的航空网络规划模型 (ANPSD)&#xff0c;该模型同时考虑了航线选择、航班频率和机队组成等问题&#xff0c;还捕捉了航空公司的供应和乘客需求之间的相互依赖关系。作者将需求实证函数与 ANPSD 模型相结合&#xff0c;开发了…

代码随想录算法训练营第四十四天|完全背包、518. 零钱兑换 II 、377. 组合总和 Ⅳ

完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题唯一不同…