• Java HashMap和ConcurrentHashMap知识点梳理


    1. jdk 8 HashMap 扩容之后旧元素存放位置是?
      java 在扩容的时候会创建一个新的 Node<K,V>[],用于存放扩容之后的值,并将旧的Node数组(其大小记作n)置空;至于旧值移动到新的节点的时候存放于哪个节点是根据 (e.hash & oldCap) == 0 来判断的:
      ① 等于0时,则将其索引位置h不变;
      ② 不等于0时,则将该节点在新数组的索引位置等于原索引位置加上旧数组长度。

    2. Java 中的另一个线程安全的、与 HashMap 极其类似的类是什么?同样是线程安全,它与 Hashtable 在线程同步上有什么不同?
      ConcurrentHashMap 类(是 Java并发包 java.util.concurrent 中提供的一个线程安全且高效的 HashMap 实现)。Hashtable 是使用 synchronized 关键字加锁的原理(就是对对象加锁);而针对 ConcurrentHashMap,在 JDK 7 中采用分段锁的方式,而JDK 8 中直接采用了CAS(无锁算法)+ synchronized。

    3. HashMap & ConcurrentHashMap 的区别?
      除了加锁,原理上无太大区别。另外,HashMap 的键值对允许有null,但是ConCurrentHashMap 都不允许。

    4. 为什么 ConcurrentHashMap 比 Hashtable 效率要高?
      Hashtable 使用一把锁(锁住整个链表结构)处理并发问题,多个线程竞争一把锁容易导致阻塞;而ConcurrentHashMap降低了锁粒度。ConcurrentHashMap在JDK 7 中使用分段锁(ReentrantLock + Segment + HashEntry),相当于把一个 HashMap 分成多个段,每段分配一把锁,这样支持多线程访问。锁粒度:基于 Segment,包含多个 HashEntry。在JDK 8 中,使用 CAS + synchronized + Node + 红黑树,锁粒度为Node(首结点)(实现 Map.Entry)。

    5. ConcurrentHashMap 在 JDK 8 中,为什么要使用内置锁 synchronized 来代替重入锁 ReentrantLock?
      ①、粒度降低了;
      ②、JVM 开发团队没有放弃 synchronized,而且基于 JVM 的 synchronized 优化空间更大,更加自然。
      ③、在大量的数据操作下,对于 JVM 的内存压力,基于 API 的 ReentrantLock 会开销更多的内存。

    6. ConcurrentHashMap 的并发度是什么?
      程序运行时能够同时更新 ConccurentHashMap 且不产生锁竞争的最大线程数。默认为 16,且可以在构造函数中设置。
      当用户设置并发度时,ConcurrentHashMap 会使用大于等于该值的最小2幂指数作为实际并发度(假如用户设置并发度为17,实际并发度则为32)

    7. ConcurrentHashMap 加锁机制
      它加锁的场景分为两种:
      1、没有发生hash冲突的时候,添加元素的位置在数组中是空的,使用CAS的方式来加入元素,这里加锁的粒度是数组中的元素。
      2、如果出现了hash冲突,添加的元素的位置在数组中已经有了值,那么又存在三种情况。
      (1)key相同,则用新的元素覆盖旧的元素。
      (2)如果数组中的元素是链表的形式,那么将新的元素挂载在链表尾部。
      (3)如果数组中的元素是红黑树的形式,那么将新的元素加入到红黑树。
      第二种场景使用的是synchronized加锁,锁住的对象就是链表头节点(红黑树的根节点)
      ,加锁的粒度和第一种情况相同。
      结论:ConcurrentHashMap分段加锁机制其实锁住的就是数组中的元素,当操作数组中不同的元素时,是不会产生竞争的。

    8. ConcurrentHashMap存储对象时(put() 方法):
      1> 如果没有初始化,就调用 initTable() 方法来进行初始化;
      2> 如果没有哈希冲突就直接 CAS 无锁插入;
      3> 如果需要扩容,就先进行扩容;
      4> 如果存在哈希冲突,就加锁来保证线程安全,两种情况:一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入;
      5> 如果该链表长度大于阀值 8(且数组中元素数量大于64),就要先转换成红黑树的结构。

    9. 下⾯这个⽅法保证了 HashMap 总是使⽤ 2 的幂作为哈希表的⼤⼩。
      /**
      * Returns a power of two size for the given target capacity.
      */
      static final int tableSizeFor(int cap) {
      int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
      return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
      }

    10. HashMap红黑树退化为链表的阈值为什么是6?
      为了避免频繁地进行链表和红黑树的转化。

    11. ConcurrentHashMap get操作需要加锁吗?线程安全吗?
      get 方法不需要加锁。因为 Node 的元素 value 和指针 next 是用 volatile 修饰的,在多线程环境下线程A修改节点的 value 或者新增节点的时候是对线程B可见的。这也是它比其它并发集合比如 Hashtable、用 Collections.synchronizedMap()包装的 HashMap 效率高的原因之一。


      读后有收获,小礼物走一走,请作者喝咖啡。

    赞赏支持

  • 相关阅读:
    Unity鼠标点击物体移动闪一下问题
    Unity中添加条形图、折线图、饼图和雷达图
    Unity实现鼠标点击物体出现其属性信息框
    Unity实现物体点击高亮
    Cinemachine实现Unity实现视角切换
    Unity连接外部摄像头
    unity实现人物按照指定路线行走
    unity如何让人物按照指定路线行走(目标点方式)
    win 10 缩放导致vs窗体_VS高分屏Winform界面变形解决方案
    MySql的安装
  • 原文地址:https://www.cnblogs.com/east7/p/14722934.html
Copyright © 2020-2023  润新知