并发映射--ConcurrentMap

Java集合系列

ConcurrentMap

一.概述


ConcurrentMap及其子类是JDK1.5提供的一套用于应对高并发的映射机制,在高并发时能比较好地保证线程安全。
 

二. ConcurrentHashMap(并发哈希映射)


  1. 性质(其下前4点同HashMap的性质)

    • 底层基于数组+链表的结构实现

    • 数组的默认容量16(表示有16个桶)

    • 默认加载因子:0.75

    • 默认扩容方法:增加一倍的桶容量(13个桶的时候开始扩容,12只是个边界值,非扩容点)

    • 若给定初始值,则会在底层有以下判断(源码)

      • 其中MAXIMUM_CAPACITY=230 可以看出,ConcurrentHashMap最多允许存在230个桶。

      • 若初始值 >=230-1 则给最大容量,否则按运算规则计算真实容量。经计算,若初始值给19,则实际容量为32 ;若初始值给48则实际容量为128。理论上可证,给出任意一个初始值,其真实大小一定是2的n次方。进一步可以发现:

      • 注意:向下取整

      \[ 19 \rightarrow 19+1+\lfloor19/2\rfloor=29 \rightarrow 16<29<32\rightarrow32\\ 48 \rightarrow 48+1+\lfloor48/2\rfloor=73 \rightarrow 64<73<128\rightarrow 128 \]

    • 与ConcurrentHashMap不同,HashMap直接拿值进入tableSizeFor计算,有兴趣可以看看源码。

    🌞 问:若给定初始容量为100,在极端情况下,ConcurrentHashMap需不需要扩容?HashMap需不需要扩容?

    【注】极端条件:元素都放在桶里

    • ConcurrentHashMap:

    \[100+1+\lfloor100\div2\rfloor=151\\ 128<151<256\\ 256\times0.75>100 \]

    => 不需要扩容

    • HashMap:

    \[64<100<128\\ 128\times0.75<100 \]

    => 需要扩容

     

  2. 在JDK1.8中,为了提高效率引入了红黑树机制,当桶中的元素个数超过8时,这个桶的链表扭转成一颗红黑树。如果红黑树的节点个数不足7个时,红黑树扭转回链表。

    • 扭转成红黑树的必要前提:桶的数量>=64

    🌙红黑树(本质上是一棵自平衡二叉查找树)

    • :一种数据结构,每个结点有零个或多个子结点;没有父结点的结点称为根结点;每一个非根结点有且只有一个父结点;除了根结点外,每个子结点可以分为多个不相交的子树;
    • 二叉树:每个结点最多只能有两棵子树,且有左右之分
    • 二叉查找树(二叉排序树)
      1. 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
      2. 若右子树不空,则右子树上所有结点的值均大于它的根结点的值;
      3. 左、右子树也分别为二叉排序树;
      4. 没有键值相等的结点。
    • 平衡二叉查找树:叶节点高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。(平衡的特点)
    • 红黑树(自平衡二叉树)
      1. 所有节点非红即黑。
      2. 根节点为黑。
      3. 红节点的子节点一定为黑(反之不成立)。
      4. 叶子节点是黑色的空节点。
      5. 从根节点到叶子节点经过的路径中黑色节点个数一致,即黑节点高度一致。
      6. 新添节点的颜色一定是红的。(与第三点有可能相背,这时就需要修正了)
      7. 需要在代码上手动实现自平衡的特点(修正)
    • 红黑树在构建过程中,每次添加一个新的节点都需要考虑是否需要修正:
      • 涂色
      • 左旋
      • 右旋
    • 时间复杂度:Olog(n)
    • 异步线程安全
      • HashMap-异步线程不安全。因为没有锁,可能发生两个线程操作同一个链。

      • Hashtable-对外提供的所有方法都加了锁(除了构造方法),锁对象是this。因此,其会把整个映射都锁起来,虽然线程安全了,但是只能同步访问,效率低下。

      • ConcurrentHashMap-采用了分段(桶)锁机制,在后序版本中,ConcurrentHashMap在分段锁基础上引入了读写锁机制:

        • 读锁:允许多个线程同时读,但是读的过程中不允许有写操作。
        • 写锁:只允许一个线程写,不允许线程读。
      • CAS-ConcurrentHashMap利用锁保证线程安全,但是在使用锁的时候,造成CPU的资源浪费(例如线程调度,线程上下文切换等)在JDK1.8中,考虑到锁带来的开销,引入了无锁算法CAS(Compare And Swp)

        • 官方解释:我认为V的值应该是A,如果是,那么将V的值更新为B,否则不修改并告诉V的实际值是多少。

          其中:V代表内存值,A代表旧的预期值,B代表新的预期值。

          其目的在于判断欲修改的值是否被修改过。

        • CAS的过程一旦被打断,就会重新开始。以确保操作的原子性。

        • 因为CAS涉及到线程的重新调度问题,所以需要结合具体的cpu内核架构来涉及,因此Java的CAS底层是依靠C语言实现的。目前市面上,服务器流行的内核架构都是支持CAS。

 

三. ConcurrentNavigableMap(并发导航映射)


  1. 提供了用于截取子映射的方法

  2. ConcurrentNavigableMap是一个接口,一般使用的是它的实现类:ConcurrentSkipListMap:并发跳跃映射表。底层基于跳跃表实现。

  3. 跳跃表

    1. 针对有序列表进行操作。
    2. 适用于查询多而增删少的场景。
    3. 以空间换时间。(构建跳跃表)
    4. 跳跃表允许进行多层提取,最上层元素个数不能少于两个。
    5. 如果在跳跃表中新添了元素,在考虑是否提取到上层的跳跃表中遵循"抛硬币"原则。
    6. 时间复杂度:O(logn)
  4. 几个方法和性值

posted @ 2020-06-17 19:13  仰观云  阅读(1320)  评论(0编辑  收藏  举报
Live2D