• 并发容器之CopyOnWriteArrayList


    Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。

    什么是CopyOnWrite容器

      CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

    CopyOnWriteArrayList的实现原理

      在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的。以下代码是向CopyOnWriteArrayList中add方法的实现(向CopyOnWriteArrayList里添加元素),可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
         * Appends the specified element to the end of this list.
         *
         * @param e element to be appended to this list
         * @return <tt>true</tt> (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;
            setArray(newElements);
            return true;
        finally {
            lock.unlock();
        }
        }

       读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。

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

       JDK中并没有提供CopyOnWriteMap,我们可以参考CopyOnWriteArrayList来实现一个,基本代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    import java.util.Collection;
    import java.util.Map;
    import java.util.Set;
     
    public class CopyOnWriteMap<K, V> implements Map<K, V>, Cloneable {
        private volatile Map<K, V> internalMap;
     
        public CopyOnWriteMap() {
            internalMap = new HashMap<K, V>();
        }
     
        public V put(K key, V value) {
     
            synchronized (this) {
                Map<K, V> newMap = new HashMap<K, V>(internalMap);
                V val = newMap.put(key, value);
                internalMap = newMap;
                return val;
            }
        }
     
        public V get(Object key) {
            return internalMap.get(key);
        }
     
        public void putAll(Map<? extends K, ? extends V> newData) {
            synchronized (this) {
                Map<K, V> newMap = new HashMap<K, V>(internalMap);
                newMap.putAll(newData);
                internalMap = newMap;
            }
        }
    }

       实现很简单,只要了解了CopyOnWrite机制,我们可以实现各种CopyOnWrite容器,并且在不同的应用场景中使用。

    CopyOnWrite的应用场景

    通过上面的分析,CopyOnWriteArrayList 有几个缺点: 
    1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc

    2、不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;

    CopyOnWriteArrayList 合适读多写少的场景,不过这类慎用 
    因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。


    CopyOnWriteArrayList透露的思想


    如上面的分析CopyOnWriteArrayList表达的一些思想: 
    1、读写分离,读和写分开 
    2、最终一致性 
    3、使用另外开辟空间的思路,来解决并发冲突

  • 相关阅读:
    GC原理---垃圾收集算法
    GC原理---对象可达判断
    散列算法和哈希表结构
    桶排序
    Spring事务梳理
    AQS
    重入锁
    CAS
    研究一下phpspider
    用php写爬虫去爬数据
  • 原文地址:https://www.cnblogs.com/baizhanshi/p/7699811.html
Copyright © 2020-2023  润新知