ConcurrentHashMap为什么广泛使用?回答这个问题之前先要回忆下几个基本的概念涉及hash的几个数据结构及锁优化(关于锁优化参考JMM之Java中锁概念的分类总结 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com))。

  哈希表就是一种以 键-值(key-indexed) 存储数据的结构, 我们只要输入待查找的值即 key, 即可查找到其对应的值。这个key就是hash值。

  哈希的思路很简单:如果所有的键都是整数,那么就可以使用一个简单的无序数组来实现——将键作为索引, 值即为其对应的值, 这样就可以快速访问任意键的值。这是对于简单的键的情况, 我们将其扩展到可以处理更加复杂的类型的键。

  链式哈希表从根本上说是由一组链表构成。 每个链表都可以看做是一个“桶”,我们将所有的元素通过散列的方式放到具体的不同的桶中。 插入元素时, 首先将其键传入一个哈希函数(该过程称为哈希键) , 函数通过散列的方式告知元素属于哪个“桶”, 然后在相应的链表头插入元素。 查找或删除元素时, 用同们的方式先找到元素的“桶”, 然后遍历相应的链表, 直到发现我们想要的元素。 因为每个“桶”都是一个链表, 所以链式哈希表并不限制包含元素的个数。 然而, 如果表变得太大, 它的性能将会降低。
    

  就HashMap而言,其是线程不安全的,在多线程环境下,使用 Hashmap 进行put 操作会引起死循环,导致 CPU 利用率接近 100%,所以在并发情况下不能使用 HashMap。

  HashTable 和 HashMap 的实现原理几乎一样,差别在于:

    1)HashTable 不允许 key 和 value 为 null

    2)HashTable 是线程安全的但是 HashTable 线程安全的策略实现代价却太大了,简单粗暴,get/put 所有相关操作都是 synchronized 的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差。

  为解决并发环境线程安全问题ConcurrentHashMap诞生了。

  JDK1.7和JDK1.8对ConcurrentHashMap实现原理并不一样,下面分别介绍:

  JDK1.7中

    ConcurrentHashMap重要的一个设计思想就是利用了锁优化中的减小锁粒度的方法思想。减小锁粒度是指缩小锁定对象的范围,从而减小锁冲突的可能性,从而提高系统的并发能力。减小锁粒度是一种削弱多线程锁竞争的有效手段。对于 HashMap 而言,最重要的两个方法是 get 与 set 方法,如果我们对整个 HashMap 加锁,可以得到线程安全的对象,但是加锁粒度太大。

      ConcurrentHashMap在jdk1.7中就从加锁对象入手解决问题,采用了数组+Segment+分段锁的方式实现。它内部细分了若干个小的 HashMap,称之为段(Segment 的大小也被称为 ConcurrentHashMap 的并发度)。默认情况下一个 ConcurrentHashMap 被进一步细分为 16 个段,也就是锁的并发度。    

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

    ConcurrentHashMap结构如下图:

      

    图中观察可知ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。 Segment 是一种可重入锁 ReentrantLock,在 ConcurrentHashMap 里扮演锁的角色, HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一个 Segment 数组, Segment 的结构和 HashMap类似,是一种数组和链表结构, 一个 Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素, 每个 Segment 守护一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得它对应的 Segment 锁。同时从上面的结构我们可以了解到,ConcurrentHashMap 定位一个元素的过程需要进行两次 Hash 操作。第一次 Hash 定位到 Segment,第二次 Hash 定位到元素所在的链表的头部。

    具体实现技术总结:ReentrantLock+Segment+HashEntry

   JDK1.8以后

  HashMap其自身进行了优化,采用了数组+链表+红黑树的实现方式来设计(参考JDK中HashMap的实现 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)),内部大量采用 CAS 操作(参考Java并发基础之Compare And Swap/Set(CAS) - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com))。其彻底放弃了 Segment 转而采用的是 Node,其设计思想也不再是JDK1.7 中的分段锁思想。

  先看看ConcurrentHashMap数据结构:

      

  1.数据结构:取消了 Segment 分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。

  2.保证线程安全机制:JDK1.7 采用 segment 的分段锁机制实现线程安全,其中segment 继承自 ReentrantLock。JDK1.8 采用 CAS+Synchronized 保证线程安全。

    在ConcurrentHashMap各功能算法中,很多采用方法确保线程安全。

  3.锁的粒度:原来是对需要进行数据操作的 Segment 加锁,现调整为对每个数组元素加锁(Node)。

  4.链表转化为红黑树:定位结点的 hash 算法简化会带来弊端,Hash 冲突加剧,因此在链表节点数量大于 8 时,会将链表转化为红黑树进行存储。

  5.查询时间复杂度:从原来的遍历链表 O(n),变成遍历红黑树 O(logN)。



 





  

  



JUC之认识ConcurrentHashMap的更多相关文章

  1. JUC回顾之-ConcurrentHashMap源码解读及原理理解

    ConcurrentHashMap结构图如下: ConcurrentHashMap实现类图如下: segment的结构图如下: package concurrentMy.juc_collections ...

  2. JUC集合之 ConcurrentHashMap

    ConcurrentHashMap介绍 ConcurrentHashMap是线程安全的哈希表. HashMap, Hashtable, ConcurrentHashMap之间的关联如下: HashMa ...

  3. Java多线程系列--“JUC集合”04之 ConcurrentHashMap

    概要 本章是JUC系列的ConcurrentHashMap篇.内容包括:ConcurrentHashMap介绍ConcurrentHashMap原理和数据结构ConcurrentHashMap函数列表 ...

  4. 最强Java并发编程详解:知识点梳理,BAT面试题等

    本文原创更多内容可以参考: Java 全栈知识体系.如需转载请说明原处. 知识体系系统性梳理 Java 并发之基础 A. Java进阶 - Java 并发之基础:首先全局的了解并发的知识体系,同时了解 ...

  5. java核心-多线程(1)-知识大纲

    Thread,整理一份多线程知识大纲,大写意 1.概念介绍 线程 进程 并发 2.基础知识介绍 Java线程类 Thread 静态方法&实例方法 Runnable Callable Futur ...

  6. java的各种集合为什么不安全(List、Set、Map)以及代替方案

    我们已经知道多线程下会有各种不安全的问题,都知道并发的基本解决方案,这里对出现错误的情况进行一个实际模拟,以此能够联想到具体的生产环境中. 一.List 的不安全 1.1 问题 看一段代码: publ ...

  7. Java集合多线程安全

    线程安全与不安全集合 线程不安全集合: ArrayList LinkedList HashMap HashSet TreeMap TreeSet StringBulider 线程安全集合: Vecto ...

  8. JUC源码分析-集合篇(一)ConcurrentHashMap

    JUC源码分析-集合篇(一)ConcurrentHashMap 1. 概述 <HashMap 源码详细分析(JDK1.8)>:https://segmentfault.com/a/1190 ...

  9. 【JUC】JDK1.8源码分析之ConcurrentHashMap(一)

    一.前言 最近几天忙着做点别的东西,今天终于有时间分析源码了,看源码感觉很爽,并且发现ConcurrentHashMap在JDK1.8版本与之前的版本在并发控制上存在很大的差别,很有必要进行认真的分析 ...

随机推荐

  1. PingFang(苹方)字体的引用

    原文 链接:https://pan.baidu.com/s/1rw39Yqo9fv9BYz_JZ5lyRw 提取码:o7kf 苹方-简 常规体 font-family: pingFangSC-Regu ...

  2. Python网络编程之网络基础

    Python网络编程之网络基础 目录 Python网络编程之网络基础 1. 计算机网络发展 1.1. OSI七层模型 1.2. 七层模型传输数据过程 2. TCP/IP协议栈 2.1 TCP/IP和O ...

  3. 端口状态 LISTENING、ESTABLISHED、TIME_WAIT及CLOSE_WAIT详解,以及三次握手四次挥手,滑动窗口(整理转发)

    网上查了一下端口状态的资料,我下面总结了一下,自己学习学习: TCP状态转移要点 TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断开连接,如果缺少了其中某个步骤,将会使连接处于假死 ...

  4. php中使用CURL之php curl详解

    curl是个什么东西?简单地说就是,curl是一个库,能让你通过URL和许多不同种的服务器进行勾搭.搭讪和深入交流,并且还支持许多协议.并且人家还说了curl可以支持https认证.http post ...

  5. Visual Studio 2022(VS2022)激活密钥

    Visual Studio 2022(VS2022) 激活码: 专业版 Pro: TD244-P4NB7-YQ6XK-Y8MMM-YWV2J 企业版 Enterprise: VHF9H-NXBBB-6 ...

  6. 使用Express连接mysql详细教程(附项目的完整代码我放在结尾了)

    使用Express连接mysql详细教程(附项目的完整代码我放在结尾了) 要使用Express连接本地数据库 我们首先需要安装好Express的依赖 我们使用这个框架呢首先要有一点ajax的基础 如果 ...

  7. pytest文档5-参数化parametrize

    pytest.mark.parametrize装饰器可以实现测试用例参数化. parametrizing 1.这里是一个实现检查一定的输入和期望输出测试功能的典型例子 # content of tes ...

  8. 使用 ES Module 的正确姿势

    前面我们在深入理解 ES Module 中详细介绍过 ES Module 的工作原理.目前,ES Module 已经在逐步得到各大浏览器厂商以及 NodeJS 的原生支持.像 vite 等新一代的构建 ...

  9. Servlet-斜杠在web中不同意义

    Servlet-斜杠在web中不同意义 在web中 / 斜杠是一种绝对路径 / 斜杠 如果被浏览器解析,得到的地址是:http://ip/port/ / 斜杠 如果被服务器解析,得到的地址是:http ...

  10. Servlet-IDEA菜单生成Servlet程序

    更快捷方法生成Servlet程序(IDEA菜单生成Servlet) 菜单:new ---> Servlet程序 取消注解做这个配置 可以创建Servlet程序,编写HelloServlet3类, ...