美文网首页
Java并发(07)并发容器CopyOnWriteArrayLi

Java并发(07)并发容器CopyOnWriteArrayLi

作者: 小亮__ | 来源:发表于2019-06-27 07:19 被阅读0次

    写入时复制(CopyOnWrite)思想

    写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

    介绍

    Java 中CopyOnWriteArrayList就是对写入时复制思想的实现,它Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可以称这种容器为”写时复制器”,Java并发包中类似的容器还有CopyOnWriteSet。

    原理

    我们都知道,集合框架中的ArrayList是非线程安全的,Vector虽是线程安全的,但由于简单粗暴的锁同步机制,性能较差。而CopyOnWriteArrayList则提供了另一种不同的并发处理策略(当然是针对特定的并发场景)。

    很多时候,我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。

    优点

    读操作性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java的list在遍历时,若中途有别的线程对list容器进行修改,则会抛出ConcurrentModificationException异常。而CopyOnWriteArrayList由于其”读写分离”的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了

    缺点

    缺点也很明显,一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份,数据量大时,对内存压力较大,可能会引起频繁GC;二是无法保证实时性,Vector对于读写操作均加锁同步,可以保证读和写的强一致性。而CopyOnWriteArrayList由于其实现策略的原因,写和读分别作用在新老不同容器上,在写操作执行过程中,读不会阻塞但读取到的却是老容器的数据。

    源码解析

    add 方法:先将原容器copy一份,然后在新副本上执行写操作,之后再切换引用。当然此过程是要加锁的

    public boolean add(E e) {
            //ReentrantLock加锁,保证线程安全
            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(); //解锁
            }
        }  
    

    remove 方法:删除操作同理,将除要删除元素之外的其他元素拷贝到新副本中,然后切换引用,将原容器引用指向新副本。同属写操作,需要加锁

    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)
                    //如果要删除的是列表末端数据,拷贝前len-1个数据到新副本上,再切换引用
                    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(); //解锁
            }
        }
    

    get方法:CopyOnWriteArrayList的读操作是不用加锁的,性能很高

    public E get(int index) {
            return get(getArray(), index);
    }
    

    相关文章

      网友评论

          本文标题:Java并发(07)并发容器CopyOnWriteArrayLi

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