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. Docker 与 K8S学习笔记(十 二)容器间数据共享

    数据共享是volume的关键特性,今天我们来看一下通过volume实现容器与host.容器与容器之间共享数据. 一.容器与host共享数据 在上一篇中介绍到的bind mount和docker man ...

  2. bit操作常见trick

    x&(x-1)可以消去最右边的1, 如果判断一个数是否是2的指数的快捷方法,比如8,二进制位1000, 那么8&(8-1)为0,只要为0就是2的指数

  3. 5种高大上的yml文件读取方式,你知道吗?

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 在上一篇文章中,我们从源码角度分析了SpringBoot解析yml配置文件的全流程,那么我们今天就来点实战,总结一下除了烂大街的@Value和@ ...

  4. 程序员必备的编程助手!SmartCoder助你轻松集成HMS Core

    当开发者在集成HMS Core遇到一些疑问时,需要翻阅官网文档,反复查看集成说明或者API调用说明,或者研究GitHub上的开源示例代码,花费较多的时间,在IDE环境和网页浏览器之间反复切换也会耗费很 ...

  5. echart的x轴或y轴区间标签如何从大到小排列

    1.有时候我们做echart时,从后台接收返回回来的数据,没有按顺序排列,这里我遇到的是区间的值,看图 我这里是处理好了的,一开始,50-100这个区间在数组的最后一列,也就是在150-200后面的这 ...

  6. SSM项目使用拦截器实现登录验证功能

    SSM项目使用拦截器实现登录验证功能 登录接口实现 public User queryUser(String UserName, String Password,HttpServletRequest ...

  7. Servlet-请求的分发处理

    1,HelloServlet类中 2,a.html中

  8. StringBuffer类(增删改查及长度可变原理)

    1 package cn.itcast.p2.stringbuffer.demo; 2 3 public class StringBufferDemo { 4 5 public static void ...

  9. 多线程-守护线程-setDaemon

    1 package multithread4; 2 /* 3 * 停止线程: 4 * 1,stop方法. 5 * 6 * 2,run方法结束. 7 * 8 * 怎么控制线程的任务结束呢? 9 * 任务 ...

  10. 免费注册香港Apple ID

    注册 一.海外Apple ID的好处 1.APP软件资源多,比如传说对决(海外版王者荣耀).海外版抖音等,这些APP软件在国内的apple store是没有的: 2.部分APP软件在海外Apple s ...