zoukankan      html  css  js  c++  java
  • 并发容器之CopyOnWriteArrayList分析

           今天介绍的主角是CopyOnWriteArrayList类,是jdk1.5才加入的一个并发集合类,它是ArrayList的Thread-safe的变体,属于COW的一种,COW系列的还有CopyOnWriteArraySet集合。COW是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。

    先给出结论:

           CopyOnWriteArrayList适用于读操作比写操作多很多的并发场景。如果读操作和写操作的频次相差不大时,建议使用Collections.synchornizedList。

    先看CopyOnWriteArrayList的类声明

    public class CopyOnWriteArrayList<E>
    extends Object
    implements List<E>, RandomAccess, Cloneable, Serializable
    

      

    1. CopyOnWriteArrayList的由来

         CopyOnWriteArrayList是在Jdk1.5的concurrent包中引入的,concurrent包的类都是为了高效并发才引入的。Jdk1.5以前,针对并发场景,能使用List的方式只能通过Collections.synchornizedList方式产生或是自己使用synchronized关键字(Vector类,因效率过低已被废弃)来实现。我们知道容器在多线程读与读之间是并不存在资源竞争的。所以直接使用synchornized实现,在某些场景下,并不高效。由此就产生了CopyOnWriteArrayList。

    2. CopyOnWriteArrayList与Collections.synchornizedList的性能比较

    上代码

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    import java.util.concurrent.*;
    
    /**
     * Created by Administrator on 2017/9/4.
     */
    public class ListDemo {
    
        private static final int SIZE = 10000;
    
        public static long testAddList(List<Integer> list){
            long startTime = System.currentTimeMillis();
            for(int i = 0; i <SIZE; i++){
                list.add(i);
            }
            long time= System.currentTimeMillis() - startTime;
            return time;
        }
    
        public static long testGetList(List<Integer> list){
            long start = System.currentTimeMillis();
            for (int i = 0; i < SIZE; i++) {
                list.get(i);
            }
            long time= System.currentTimeMillis() - start;
            return time;
        }
    
    
        public static void main(String [] args){
            ArrayList<Integer> list = new ArrayList<>();
            List<Integer> list2 = Collections.synchronizedList(list);
            CopyOnWriteArrayList<Integer> list3 = new CopyOnWriteArrayList<>();
    
            //多线程测试性能;
            long addSynchronizedListTime = 0L, addCopyOnWriteArrayListTime = 0L;
            long getSynchronizedListTime = 0L, getCopyOnWriteArrayListTime = 0L;
            ExecutorService service = Executors.newCachedThreadPool();
    
            //测试synchornizedList的写和读操作的性能;
            for(int i = 0 ; i <5; i++) {
                try {
                    addSynchronizedListTime += service.submit(new AddDataRunnable(list2)).get();
                    getSynchronizedListTime += service.submit(new GetDataRunnable(list2)).get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
    
            //测试CopyOnWriteArrayList的写和读操作性能;
            for(int i = 0 ; i <5; i++) {
                try {
                    addCopyOnWriteArrayListTime += service.submit(new AddDataRunnable(list3)).get();
                    getCopyOnWriteArrayListTime += service.submit(new GetDataRunnable(list3)).get();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
    
            System.out.println("addSynchornizedTime:"+addSynchronizedListTime);
            System.out.println("getSynchornizedTime:"+getSynchronizedListTime);
            System.out.println("addCopyOnWriteArrayListTime:"+addCopyOnWriteArrayListTime);
            System.out.println("getCopyOnWriteArrayListTime:"+getCopyOnWriteArrayListTime);
    
        }
    
        static class AddDataRunnable implements Callable<Long>{
    
            private List<Integer> mList;
            public AddDataRunnable(List<Integer> l){
                this.mList = l;
            }
    
            @Override
            public Long call() throws Exception {
                long time = testAddList(mList);
                return time;
            }
        }
    
        static class GetDataRunnable implements Callable<Long>{
    
            private List<Integer> mList;
            public GetDataRunnable(List<Integer> l){
                this.mList = l;
            }
    
            @Override
            public Long call() throws Exception {
                long time = testGetList(mList);
                return time;
            }
        }
    
    }
    

    运行结果如下:

    addSynchornizedTime:3
    getSynchornizedTime:3
    addCopyOnWriteArrayListTime:1324
    getCopyOnWriteArrayListTime:0
    
    Process finished with exit code 0

    可以从运行结果中得出结论:

    Collections.synchronizedList的整体的读与写性能都比较稳定。而CopyOnWriteArrayList在写方面,表现的非常差,在读操作上,却非常优秀。

    所以CopyOnWriteArrayList适合使用在读操作比较多的并发场景。

    3. CopyOnWriteArrayList的代码分析

           我们接下来分析下为何CopyOnWriteArrayList有此特性,前面已经提到,针对读操作,是不做处理,和普通的ArrayList性能一样。而在写操作(修改时),会先拷贝一份,实现新旧版本的分离,然后在拷贝的版本上进行修改操作,修改完后,将其更新至就版本中。

    我们以add方法为例:

        

    /** The array, accessed only via getArray/setArray. */
        private transient volatile Object[] 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;  //对新数组执行add操作;
                setArray(newElements);//将新数组更新至arrays
                return true;
            } finally {
                lock.unlock(); //释放锁;
            }
        }
    

      再看一下get方法

    /**
         * Gets the array.  Non-private so as to also be accessible
         * from CopyOnWriteArraySet class.
         */
        final Object[] getArray() {
            return array;
        }
    
    
    /**
         * {@inheritDoc}
         *
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E get(int index) {
            return get(getArray(), index);
        }
    

      get方法中没有加锁,就是普普通通的ArrayList的get操作;

           

           这里我们就可以知道CopyOnWriteArrayList面对写操作为什么性能低下了?因为首先需要去lock,有可能需要等待时间去获取锁,还有就是每一步的写操作,都会发生Arrays.copyOf的拷贝操作。

    4. ConcurrentModificationException异常

        普通的ArrayList在遍历成员时,如果修改集合,则会报出ConcurrentModificationException异常。而CopyOnWriteArrayList的实现,在遍历时,修改并不会报出该异常。

        

    import java.util.ArrayList;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    /**
     * Created by Administrator on 2017/9/4.
     */
    public class ExceptionDemo {
    
        public static void main(String [] args){
    
    //        ArrayList<Integer> list = new ArrayList<>();
    //        list.add(1);
    //        list.add(2);
    //        list.add(3);
    //
    //        for(int a: list){
    //            list.add(5);
    //        }
    
            /*
            Exception in thread "main" java.util.ConcurrentModificationException
            at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
            at java.util.ArrayList$Itr.next(ArrayList.java:851)
            at ExceptionDemo.main(ExceptionDemo.java:18)
    
            Process finished with exit code 1
             */
    
            CopyOnWriteArrayList<Integer> l = new CopyOnWriteArrayList<>();
            l.add(1);
            l.add(2);
            l.add(3);
    
            for(int a: l){
                l.add(5);
            }
    
        }
    }
    

    5. CopyOnWriteArrayList的缺点

        COW思想,是一种实现读写分离的思想,优化了读操作的性能。因为其实现所以存在以下的缺点:

      内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,会造成GC的回收,引发性能问题。针对内存紧张的场景,建议使用其他的并发容器代替。  

      数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

    参考链接:

    http://www.cnblogs.com/dolphin0520/p/3938914.html

    http://blog.csdn.net/zljjava/article/details/48139465

  • 相关阅读:
    tornado-cookies+pycket 验证
    解决form表单通过ajax时,required失效问题
    如何判断一个文件是否存在
    django中@property装饰器的运用
    RestFul API接口设计风格介绍和核心功能(概念)
    python中各个response使用
    Python的命名规则
    break和continue的区别
    LOCK接口
    API
  • 原文地址:https://www.cnblogs.com/yw-technology/p/7476106.html
Copyright © 2011-2022 走看看