zoukankan      html  css  js  c++  java
  • 并发容器(三)非阻塞队列的并发容器

      本文将介绍除了阻塞队列外的并发容器: ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue;

    1. CopyOnWriteArrayList

    • 是 ArrayList 的线程安全的实现,同时也可用于代替 Vector 。底层实现是一个数组,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。这就是 “写时复制”。
    • 可变操作一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更 有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。
    • 迭代器在创建时,使用了数组状态的快照,此数组快照在迭代期间是不会改变的,因此也不会发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。
    • 迭代器使用的是数组的快照,所以迭代器是无法反映列表的添加、移除或者更改。
    • 在迭代器上进行的元素更改操作(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。

    看一下add()方法的源码:add()方法加了锁,添加一个元素,就是将数组的元素复制到新的数组中(新数组的大小=旧数组大小+1),再把新元素放到新数组中。remove方法也是如此。还提供原子操作 addIfAbsent() 方法。

     public void add(int index, E element) {
            final ReentrantLock lock = this.lock;
            //加锁
            lock.lock();
            try {
                //获取当前的数组
                Object[] elements = getArray();
                int len = elements.length;
                if (index > len || index < 0)
                    throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len);
                
                Object[] newElements;
                int numMoved = len - index;
                if (numMoved == 0)
                    newElements = Arrays.copyOf(elements, len + 1);//复制创建新数组
                else {
                    newElements = new Object[len + 1];
                    System.arraycopy(elements, 0, newElements, 0, index);
                    System.arraycopy(elements, index, newElements, index + 1,
                                     numMoved);
                }
                newElements[index] = element;//添加新元素到新数组中去
                setArray(newElements);//将新数组设为 当前对象的底层数组
            } finally {
                lock.unlock();
            }
        }
    

    2. CopyOnWriteArraySet

      CopyOnWriteArraySet 是 HashSet 线程安全的一个实现。CopyOnWriteArraySet 的实现是基于 CopyOnWriteArrayList,其内部维护着一个 CopyOnWriteArrayList。其特性可参考 CopyOnWriteArrayList。

    private final CopyOnWriteArrayList<E> al;
    
    /**
         * Creates an empty set.构造方法
         */
        public CopyOnWriteArraySet() {
            al = new CopyOnWriteArrayList<E>();
        }
    
        public int size() {
            return al.size();
        }
    
        public boolean isEmpty() {
            return al.isEmpty();
        }
    
        public boolean contains(Object o) {
            return al.contains(o);
        }
    
        public boolean add(E e) {
            return al.addIfAbsent(e);
        }
        //........
    

    get()方法使用的也是数组的快照,没有加锁阻塞,这就意味着get()方法返回的值不是很精确。

     public E get(int index) {
            return get(getArray(), index);
        }
    
        @SuppressWarnings("unchecked")
        private E get(Object[] a, int index) {
            return (E) a[index];
        }
    

    3. ConcurrentLinkedQueue

      一个基于链接节点的 无界线程安全队列 。此队列按照 FIFO(先进先出)原则对元素进行排序。队列的头部 是队列中时间最长的元素。队列的尾部 是队列中时间最短的元素。新的元素插入到队列的尾部,队列获取操作从队列头部获得元素。
      ConcurrentLinkedQueue 采用了非阻塞的CAS算法,在高并发的环境下,性能非常好。其源码分析可参考。

    4. ConcurrentHashMap

      ConcurrentHashMap 是 HashMap 线程安全的实现,同时也用于 代替 HashTable。(此类可以通过程序完全与 Hashtable 进行互操作,这取决于其线程安全,而与其同步细节无关。)。不同于HashTable(一张hash表只用一把锁),在ConcurrentHashMap中,会将hash表的数据分成若干段,每段维护一个锁,粒度更细,以达到高效的并发访问;

      ConcurrentHashMap 与 其他并发容器一样,在迭代的过程不需要加锁,迭代器具有弱一致性,迭代期间不会抛出ConcurrentModificationException异常,并非“立即失败”;所谓 弱一致性 ,就是返回的元素将反映迭代器创建时或创建后某一时刻的映射状态。同时,需要在整个Map上进行计算的方法,如 size()、isEmpty(),这些方法的语义被略微减弱,以反映并发的特性,换句话说,这些方法的值是一个估计值,并不是很精确。事实上,这些方法在并发环境下用处很小,因为在并发的情况下,它们的返回值总是在变化。如果需要强一致性,那么就得考虑加锁。同步容器类便是强一致性的

      由于 ConcurrentHashMap 不能被加锁来执行独占访问,因此无法通过加锁来创建新的原子操作。不过,ConcurrentHashMap 提供了以下几个原子操作(由其父接口 ConcurrentMap 提供),基本满足需求了:

    //如果指定键已经不再与某个值相关联,则将它与给定值关联。
    V putIfAbsent(K key, V value);
    
    //只有目前将键的条目映射到给定值时,才移除该键的条目。
    boolean remove(Object key, Object value);
    
    //只有目前将键的条目映射到某一值时,才替换该键的条目。
    V replace(K key, V value);
    
    //只有目前将键的条目映射到给定值时,才替换该键的条目。
    boolean replace(K key,V oldValue, V newValue);
    

    5. ConcurrentSkipListMap

      ConcurrentSkipListMap 是 TreeMap 的线程安全的实现。与上面的并发容器一样,迭代器是是弱一致性,返回的元素将反映迭代器创建时或创建后某一时刻的映射状态。它们不 抛出 ConcurrentModificationException,可以并发处理其他操作。size()操作返回的值也不是精确的。此外,批量操作 putAll、equals 和 clear 并不保证能以原子方式 (atomically) 执行 。例如,与 putAll 操作一起并发操作的迭代器只能查看某些附加元素。

    6. ConcurrentSkipListSet

       ConcurrentSkipListSet 是 TreeSet 的线程安全的实现。ConcurrentSkipListSet 是基于 ConcurrentSkipListMap 实现的,就是将所有Map的Key的值所对应的 value 值为Boolean.TRUE。其特性参考 ConcurrentSkipListMap

     private final ConcurrentNavigableMap<E,Object> m;
    
     public boolean add(E e) {
            return m.putIfAbsent(e, Boolean.TRUE) == null;
        }
    

    7. 最后总结几点:

      并发容器的性能一般都要比同步容器的性能更高。同步容器的所有公开的方法都用 synchronized 加了锁,所以同一时间只能一个线程访问同步容器。并发容器类则是锁的粒度更小(多个线程就可以并发地访问方法里面的非临界区代码,提高效率),还有的采用了 非阻塞的算法CAS(如 ConcurrentLinkedQueue),锁分段等技术。当然,这并不是说同步容器就没有用了,如希望程序的 HashSet 线程安全,可以采用 CopyOnWriteArraySet,但如果写操作多于读操作的话,那么就应该采用 Collections.synchronizedSet(Set

      并发容器是“弱一致性”,因此在迭代器创建后,不能反映容器的增、删、改的情况,同时size、isEmpty等方法只能得到一个估值,不是很精确。“弱一致性” 虽然在一些地方做出牺牲(就是上面所说的),但也极大提高了并发容器的其他方面性能,特别是迭代,迭代是可以并发进行,不需要额外的同步,也不会抛出 ConcurrentModificationException。

    同步容器是“强一致性”,所以同步容器的迭代操作都需要加锁来保证原子性操作。

    对于除了迭代操作外的复合操作,并发容器中某些类提供了常用的复合操作的原子性方法(如:ConcurrentHashMap.putIfAbsent(K key, V value) )。对于复合操作,要谨慎,特别是同步容器,记得加锁来保证原子性。


    下面是源码分析的好文章,值得一看

    1. 【JUC】JUC集合框架综述
    2. 【JUC】JDK1.8源码分析之ConcurrentHashMap(一)
    3. 【JUC】JDK1.8源码分析之ConcurrentSkipListMap(二)
    4. 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)
    5. 【JUC】JDK1.8源码分析之LinkedBlockingQueue(四)
    6. 【JUC】JDK1.8源码分析之ConcurrentLinkedQueue(五)
    7. 【JUC】JDK1.8源码分析之CopyOnWriteArrayList(六)
    8. 【JUC】JDK1.8源码分析之CopyOnWriteArraySet(七)
    9.【JUC】JDK1.8源码分析之ConcurrentSkipListSet(八)
    10.【JUC】JDK1.8源码分析之SynchronousQueue(九)

  • 相关阅读:
    如何在存储过程中临时设置数据库兼容级别
    PHP中如何防止SQL注入
    Android 源码下载
    HDU 2517 棋盘分割
    OceanBase里面的rowkey是什么概念,是由哪些要素构成的?
    JavaScript的递归之更多例子
    C++库研究笔记——生成一组随机数
    Neutron 如何支持多种 network provider
    理解 Neutron Server 分层模型
    Neutron 物理部署方案
  • 原文地址:https://www.cnblogs.com/jinggod/p/8495509.html
Copyright © 2011-2022 走看看