zoukankan      html  css  js  c++  java
  • java并发:CopyOnWrite机制

    CopyOnWriteArrayList

    java.util.concurrent.CopyOnWriteArrayList是一个线程安全的 ArrayList,其修改操作是在底层的一个复制的数组(快照)上进行的,即用了写时复制策略。

    其定义如下:

    public class CopyOnWriteArrayList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        private static final long serialVersionUID = 8673264195747942595L;
    
        /**
         * The lock protecting all mutators.  (We have a mild preference
         * for builtin monitors over ReentrantLock when either will do.)
         */
        final transient Object lock = new Object();
    
        /** The array, accessed only via getArray/setArray. */
        private transient volatile Object[] array;
    
        /**
         * Gets the array.  Non-private so as to also be accessible
         * from CopyOnWriteArraySet class.
         */
        final Object[] getArray() {
            return array;
        }

    其构造函数如下:

        /**
         * Creates an empty list.
         */
        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
         */
        public CopyOnWriteArrayList(Collection<? extends E> c) {
            Object[] es;
            if (c.getClass() == CopyOnWriteArrayList.class)
                es = ((CopyOnWriteArrayList<?>)c).getArray();
            else {
                es = c.toArray();
                if (c.getClass() != java.util.ArrayList.class)
                    es = Arrays.copyOf(es, es.length, Object[].class);
            }
            setArray(es);
        }
    
        /**
         * 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
         */
        public CopyOnWriteArrayList(E[] toCopyIn) {
            setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
        }

    解释:

    针对无参构造函数,其内部创建了一个大小为 0 的 Object 数组作为array 的初始值。

    其添加元素的方法有多个,如下:

    add(E 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) {
            synchronized (lock) {
                Object[] es = getArray();
                int len = es.length;
                es = Arrays.copyOf(es, len + 1);
                es[len] = e;
                setArray(es);
                return true;
            }
        }

    弱一致性问题

    CopyOnWriteArrayList中获取指定位置的元素的方法如下:

        /**
         * {@inheritDoc}
         *
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E get(int index) {
            return elementAt(getArray(), index);
        }
        static <E> E elementAt(Object[] a, int index) {
            return (E) a[index];
        }

    解说:

    从上述代码可以知道,当线程 x调用 get方法获取指定位置的元素时需要两步:

    • 获取 array数组(步骤A)
    • 通过下标访问指定位置的元素 (步骤B)

    这个两步操作在整个过程中并没有加锁。

    问题:假设线程 x执行完步骤 A 后,在执行步骤 B 前,线程 y进行了 remove操作,这时会发生什么?

    remove方法的代码如下:

        /**
         * 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) {
            synchronized (lock) {
                Object[] es = getArray();
                int len = es.length;
                E oldValue = elementAt(es, index);
                int numMoved = len - index - 1;
                Object[] newElements;
                if (numMoved == 0)
                    newElements = Arrays.copyOf(es, len - 1);
                else {
                    newElements = new Object[len - 1];
                    System.arraycopy(es, 0, newElements, 0, index);
                    System.arraycopy(es, index + 1, newElements, index,
                                     numMoved);
                }
                setArray(newElements);
                return oldValue;
            }
        }

    remove操作获取独占锁,然后进行写时复制操作,在复制的数组里删除 index 处的元素,随后让 array 指向新复制的数组。  

      

            

    上图指出了线程 x 执行步骤B时操作的数组是线程 y 删除元素之前线程 x 复制的数组。

    所以,线程 y 虽然己经删除了 index 处的元素,但是线程 x 的步骤 B 还是会返回index 处的元素,这就是写时复制策略的弱一致性问题。

    迭代器弱一致性

    大家都知道可以使用法代器遍历List的元素,当调用 CopyOnWriteArrayList 的 iterator()方法获取法代器时,实际上会返回一个 COWiterator对象。

        /**
         * Returns an iterator over the elements in this list in proper sequence.
         *
         * <p>The returned iterator provides a snapshot of the state of the list
         * when the iterator was constructed. No synchronization is needed while
         * traversing the iterator. The iterator does <em>NOT</em> support the
         * {@code remove} method.
         *
         * @return an iterator over the elements in this list in proper sequence
         */
        public Iterator<E> iterator() {
            return new COWIterator<E>(getArray(), 0);
        }
        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;
    
            COWIterator(Object[] es, int initialCursor) {
                cursor = initialCursor;
                snapshot = es;
            }
    
            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.
             */
            public void remove() {
                throw new UnsupportedOperationException();
            }
    
            /**
             * Not supported. Always throws UnsupportedOperationException.
             * @throws UnsupportedOperationException always; {@code set}
             *         is not supported by this iterator.
             */
            public void set(E e) {
                throw new UnsupportedOperationException();
            }
    
            /**
             * Not supported. Always throws UnsupportedOperationException.
             * @throws UnsupportedOperationException always; {@code add}
             *         is not supported by this iterator.
             */
            public void add(E e) {
                throw new UnsupportedOperationException();
            }
    
            @Override
            public void forEachRemaining(Consumer<? super E> action) {
                Objects.requireNonNull(action);
                final int size = snapshot.length;
                int i = cursor;
                cursor = size;
                for (; i < size; i++)
                    action.accept(elementAt(snapshot, i));
            }
        }

    解说:

    COWiterator对象的 snapshot变量保存了当前 array的内容,cursor是遍历数据的下标。

    在线程使用返回的迭代器遍历元素的过程中,如果有其他线程对 list 进行了增删改,那么 snapshot就是快照了。

    因为增删改后指向 array的变量(即本文最开始着色了的那行代码,private transient volatile Object[] array;)指向了新数组。 

    所以在获取迭代器遍历元素时,其他线程对 list 进行的增删改对外不可见,因为它们操作的是两个不同的数组,这就是弱一致性。 

    CopyOnWriteArraySet

    java.util.concurrent.CopyOnWriteArraySet是在CopyOnWriteArrayList的基础上使用了装饰模式,其很多方法都是调用CopyOnWriteArrayList的API来实现的。

    其添加元素的代码如下:

        /**
         * Adds the specified element to this set if it is not already present.
         * More formally, adds the specified element {@code e} to this set if
         * the set contains no element {@code e2} such that
         * {@code Objects.equals(e, e2)}.
         * If this set already contains the element, the call leaves the set
         * unchanged and returns {@code false}.
         *
         * @param e element to be added to this set
         * @return {@code true} if this set did not already contain the specified
         *         element
         */
        public boolean add(E e) {
            return al.addIfAbsent(e);
        }

    解说:

    从CopyOnWriteArraySet的定义以及构造函数可以看到其底层数据结构就是CopyOnWriteArrayList,代码如下:

    public class CopyOnWriteArraySet<E> extends AbstractSet<E>
            implements java.io.Serializable {
        private static final long serialVersionUID = 5457747651344034263L;
    
        private final CopyOnWriteArrayList<E> al;
    
        /**
         * Creates an empty set.
         */
        public CopyOnWriteArraySet() {
            al = new CopyOnWriteArrayList<E>();
        }
    
        /**
         * Creates a set containing all of the elements of the specified
         * collection.
         *
         * @param c the collection of elements to initially contain
         * @throws NullPointerException if the specified collection is null
         */
        public CopyOnWriteArraySet(Collection<? extends E> c) {
            if (c.getClass() == CopyOnWriteArraySet.class) {
                @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
                    (CopyOnWriteArraySet<E>)c;
                al = new CopyOnWriteArrayList<E>(cc.al);
            }
            else {
                al = new CopyOnWriteArrayList<E>();
                al.addAllAbsent(c);
            }
        }
  • 相关阅读:
    Socket Client & Server
    2台整机,多线程运行测试时间不稳定
    去了一趟宝马铁西工厂,才知道工厂已经在数字孪生
    窗帘电机测试系统调试心得
    AntDesignBlazor 学习笔记
    简单粗暴的实现 Blazor Server 登录鉴权
    Blazor 子组件与父组件通过 ChildEvents 传递数据的方法
    EasyExcel 列 固定下拉选项的做法
    Java代码在数据库存取某些敏感字段的加解密做法
    nacos 使用【一】 创建一个属于自己的配置空间
  • 原文地址:https://www.cnblogs.com/studyLog-share/p/5318653.html
Copyright © 2011-2022 走看看