• 集合----并发包


    一、JUC中的集合类--java.util.concurrent

        下面,我们先了解JUC包中集合类的框架;为了方便讲诉,我将JUC包中的集合类划分为3部分来进行说明。在简单的了解JUC包中集合类的框架之后,后面的章节再逐步对各个类进行介绍。

    1. List和Set

    JUC集合包中的List和Set实现类包括: CopyOnWriteArrayList、 CopyOnWriteArraySet、ConcurrentSkipListSet。ConcurrentSkipListSet稍后在说明Map时再说明,CopyOnWriteArrayList 和 CopyOnWriteArraySet的框架如下图所示:

    (01) CopyOnWriteArrayList相当于线程安全的 ArrayList,它实现了List接口。CopyOnWriteArrayList是支持高并发的。

    (02) CopyOnWriteArraySet相当于线程安全的 HashSet,它继承于AbstractSet类。CopyOnWriteArraySet内部包含一个CopyOnWriteArrayList对象,它是通过CopyOnWriteArrayList实现的。

    2. Map

    JUC集合包中Map的实现类包括: ConcurrentHashMapConcurrentSkipListMap。它们的框架如下图所示:

    (01) ConcurrentHashMap是线程安全的哈希表(相当于线程安全的HashMap);它继承于AbstractMap类,并且实现ConcurrentMap接口。ConcurrentHashMap是通过“锁分段”来实现的,它支持并发。

    (02) ConcurrentSkipListMap是线程安全的有序的哈希表(相当于线程安全的TreeMap); 它继承于AbstractMap类,并且实现ConcurrentNavigableMap接口。ConcurrentSkipListMap是通过“跳表”来实现的,它支持并发。

    (03) ConcurrentSkipListSet是线程安全的有序的集合(相当于线程安全的TreeSet);它继承于AbstractSet,并实现了NavigableSet接口。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它也支持并发。

    3. Queue

    JUC集合包中Queue的实现类包括: ArrayBlockingQueue, LinkedBlockingQueue, LinkedBlockingDeque, 

    ConcurrentLinkedQueue和ConcurrentLinkedDeque。它们的框架如下图所示:

    (01) ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列。

    (02) LinkedBlockingQueue是单向链表实现的(指定大小)阻塞队列,该队列按 FIFO(先进先出)排序元素。

    (03) LinkedBlockingDeque是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式。

    (04) ConcurrentLinkedQueue是单向链表实现的无界队列,该队列按 FIFO(先进先出)排序元素。

    (05) ConcurrentLinkedDeque是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。

    二、ConcurrentHashMap

    1、为什么要用ConcurrentHashMap:

      对于 HashMap 而言,最重要的两个方法是 get 与 set 方法,如果我们对整个 HashMap 加锁,可以得到线程安全的对象,但是加锁粒度太大。
      而ConcurrentHashMap就是通过减小锁粒度来增大结合并发的效率。减小锁粒度是指缩小锁定对象的范围,从而减小锁冲突的可能性,从而提高系统的并发能力。减小锁粒度是一种削弱多线程锁竞争的有效手段,Segment 的大小被称为 ConcurrentHashMap 的并发度。

    2、ConcurrentHashMap实现原理:

    (1)jdk1.7:

      ConcurrentHashMap,它内部细分了若干个小的 HashMap,称之为段(Segment)。默认情况下一个 ConcurrentHashMap 被进一步细分为 16 个段,既就是锁的并发度。

      如果需要在 ConcurrentHashMap 中添加一个新的表项,并不是将整个 HashMap 加锁,而是首先根据hashcode得到该表项应该存放在哪个段中,然后对该段加锁,并完成put操作。在多线程环境中,如果多个线程同时进行put操作,只要被加入的表项不存放在同一个段中,则线程间可以做到真正的并行。

      底层是有是由 Segment 数组结构 + HashEntry 数组结构 组成,Segment继承了可重入锁 ReentrantLock,在 ConcurrentHashMap 里扮演锁的角色,HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组,Segment 的结构和 HashMap类似,是一种数组和链表结构, 一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素, 每个 Segment 守护一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得它对应的 Segment 锁。

    static class Segment<K,V> extends ReentrantLock implements Serializable {
    }

     (2)jdk1.8:

      ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树
      synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

           CAS 属于原子操作的一种,能够保证一次读写操作是原子的。CAS 通过将内存中的值与期望值进行比较,只有在两者相等时才会对内存中的值进行修改。

           详细原理:https://www.codercto.com/a/57430.html

    3、ConcurrentHashMap 和 Hashtable的区别?

    (1)底层数据结构:

    ConcurrentHashMap 在JDK1.7底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。

    Hashtable 在JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;

    (2)实现线程安全的方式:

    ① 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。

       到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;

    ② Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

    详细的实现原理: https://www.jianshu.com/p/865c813f2726

    4、为什么会抛弃分段锁?

    (1)分段Segment继承了重入锁ReentrantLock,有了锁的功能,每个锁控制的是一段,当每个Segment越来越大时,锁的粒度就变得有些大了

    • 分段锁的优势在于保证在操作不同段 map 的时候可以并发执行,操作同段 map 的时候,进行锁的竞争和等待。这相对于直接对整个map同步synchronized是有优势的。
    • 缺点在于分成很多段时会比较浪费内存空间(不连续,碎片化); 操作map时竞争同一个分段锁的概率非常小时,分段锁反而会造成更新等操作的长时间等待; 当某个段很大时,分段锁的性能会下降。

    (2)舍弃ReentrantLock而用synchronized

    • 减少内存开销:如果使用ReentrantLock则需要节点继承AQS来获得同步支持,增加内存开销,而1.8中只有头节点需要进行同步。
    • 内部优化:synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等。

    参考博客:https://blog.csdn.net/QYHuiiQ/article/details/94325601

  • 相关阅读:
    我的学习之路_第八章_map集合
    我的学习之路_第七章_list集合,set集合
    我的学习之路_第六章_迭代器,泛型
    我的学习之路_第五章_Data,正则
    我的学习之路_第四章_异常
    我的学习之路_第三章_匿名内部类
    我的学习之路_第二章_接口/多态
    如何使用电脑上的谷歌浏览器来调试安卓手机上的移动端页面
    跨域之jsonp
    跨域之同源策略 Same-origin policy
  • 原文地址:https://www.cnblogs.com/guoyu1/p/12188322.html
Copyright © 2020-2023  润新知