zoukankan      html  css  js  c++  java
  • 理解CopyOnWriteArrayList

    CopyOnWriteArrayList,顾名思义,Write的时候总是要Copy,也就是说对于任何可变的操作(add、set、remove)都是伴随复制这个动作的

    A thread-safe variant of ArrayList in which all mutative operations (addset, and so on) are implemented by making a fresh copy of the underlying array.

    This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot

    or don't want to synchronize traversals, yet need to preclude interference among concurrent threads. The "snapshot" style iterator method uses a reference

    to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible

    and the iterator is guaranteed not to throw ConcurrentModificationException. The iterator will not reflect additions, removals, or changes to the list since

    the iterator was created. Element-changing operations on iterators themselves (removeset, and add) are not supported. These methods throw

     UnsupportedOperationException. All elements are permitted, including null.

    一.初始化

        /** The lock protecting all mutators */
        final transient ReentrantLock lock = new ReentrantLock();
    
        /** 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;
        }
    
        /**
         * Sets the array.
         */
        final void setArray(Object[] a) {
            array = a;
        }
    
        /**
         * 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[] elements;
            if (c.getClass() == CopyOnWriteArrayList.class)
                elements = ((CopyOnWriteArrayList<?>)c).getArray();
            else {
                elements = c.toArray();
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elements.getClass() != Object[].class)
                    elements = Arrays.copyOf(elements, elements.length, Object[].class);
            }
            setArray(elements);
        }

    二.如何添加元素

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

    添加一个整数2

    1、加锁

    2、实例化出一个新的数组,size=原数组+1

    3、把原数组的元素复制到新数组中去

    4、新数组最后一个位置设置为待添加的元素

    5、把Object array引用指向新数组

    6、解锁

    三。使用


    static class ReadThread extends Thread { private List<Integer> list; public ReadThread(List<Integer> list) { this.list = list; } public void run() { for (Integer i : list) { } } } static class WriteThread extends Thread { private List<Integer> list; public WriteThread(List<Integer> list) { this.list = list; } public void run() { for (int i = 0; i < list.size(); i++) { list.remove(i); } } }
            List<Integer> list = new ArrayList<>();
    
            for (int i = 0; i < 10000; i++) {
                list.add(i);
            }
    
            ReadThread readThread = new ReadThread(list);
            WriteThread writeThread = new WriteThread(list);
            readThread.start();
            writeThread.start();

    Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.ys.scs.db.RSAUtils$ReadThread.run(RSAUtils.java:161)

    改成vector也不行

    Vector虽然是线程安全的,但是只是一种相对的线程安全而不是绝对的线程安全,它只能够保证增、删、改、查的单个操作一定是原子的,不会被打断,

    但是如果组合起来用,并不能保证线程安全性。比如就像上面的线程1在遍历一个Vector中的元素、线程2在删除一个Vector中的元素一样,势必产生并

    发修改异常,也就是fail-fast。

    CopyOnWriteArrayList的缺点,就是修改代价十分昂贵,每次修改都伴随着一次的数组复制;随着CopyOnWriteArrayList中元素的增加,

    CopyOnWriteArrayList的修改代价将越来越昂贵,因此,CopyOnWriteArrayList适用于读操作远多于修改操作的并发场景中

    但同时优点也十分明显,就是在并发下不会产生任何的线程安全问题,也就是绝对的线程安全,这也是为什么我们要使用CopyOnWriteArrayList的原因。

    四、总结

    CopyOnWriteArrayList这个并发组件,其实反映的是两个十分重要的分布式理念:

    (1)读写分离

    我们读取CopyOnWriteArrayList的时候读取的是CopyOnWriteArrayList中的Object[] array,但是修改的时候,操作的是一个新的Object[] array,读和写

    操作的不是同一个对象,这就是读写分离。这种技术数据库用的非常多,在高并发下为了缓解数据库的压力,即使做了缓存也要对数据库做读写分离,读

    的时候使用读库,写的时候使用写库,然后读库、写库之间进行一定的同步,这样就避免同一个库上读、写的IO操作太多

    (2)最终一致

    对CopyOnWriteArrayList来说,线程1读取集合里面的数据,未必是最新的数据。因为线程2、线程3、线程4四个线程都修改了CopyOnWriteArrayList里面

    的数据,但是线程1拿到的还是最老的那个Object[] array,新添加进去的数据并没有,所以线程1读取的内容未必准确。不过这些数据虽然对于线程1是不一致

    的,但是对于之后的线程一定是一致的,它们拿到的Object[] array一定是三个线程都操作完毕之后的Object array[],这就是最终一致。最终一致对于分布

    式系统也非常重要,它通过容忍一定时间的数据不一致,提升整个分布式系统的可用性与分区容错性。当然,最终一致并不是任何场景都适用的,像火车站

    售票这种系统用户对于数据的实时性要求非常非常高,就必须做成强一致性的。

     出处:
    http://www.importnew.com/25034.html
  • 相关阅读:
    如何解决无法成功git commit 和git push
    orleans 项目调试注意
    silo 主机 配置
    asp.net core 项目引用包版本问题
    C# async 方法怎么被正确的消费 (新篇)
    C# 虚方法 复习
    C# dynamic 适用场景进一步说明
    [MySQL]
    C# Subject 观察者模式
    C# 协变与逆变
  • 原文地址:https://www.cnblogs.com/yuyutianxia/p/7047443.html
Copyright © 2011-2022 走看看