文章目录

  • 1、前言
  • 2、什么是ConcurrentHashMap
  • 3、Put 操作
  • 4、Get 操作
  • 5、高并发线程安全
  • 6、JDK8 的改进
    • 6.1 结构改变
    • 6.2 HashEntry 改为 Node
    • 6.3 Put 操作的变化
    • 6.4 Get 操作的变化
    • 6.5 总结

1、前言

  学习本章之前,先学习:深入浅出HashMap详解(JDK7)

  简单回顾一下 HashMap 的结构:

  在 JDK7 下,高并发时,有可能出现下面的环形链表:

  要避免 HashMap 的线程安全问题,有多个解决方法,比如改用 HashTable 或者 Collections.synchronizedMap() 方法。

  但是这两者都有一个问题,就是性能,无论读还是写,他们两个都会给整个集合加锁,导致同一时间的其他操作阻塞。

  ConcurrentHashMap 的优势在于兼顾性能和线程安全,一个线程进行写操作时,它会锁住一小部分,其他部分的读写不受影响,其他线程访问没上锁的地方不会被阻塞。

2、什么是ConcurrentHashMap

  java.util.concurrent.ConcurrentHashMap属于 JUC 包下的一个集合类,可以实现线程安全。

  它由多个 Segment 组合而成。Segment 本身就相当于一个 HashMap 对象。同 HashMap 一样,Segment 包含一个 HashEntry 数组,数组中的每一个 HashEntry 既是一个键值对,也是一个链表的头节点。

  单一的 Segment 结构如下:

  像这样的 Segment 对象,在 ConcurrentHashMap 集合中有多少个呢?有 2 的 N 次方个,共同保存在一个名为 segments 的数组当中。

  因此整个ConcurrentHashMap的结构如下:

  可以说,ConcurrentHashMap 是一个二级哈希表。在一个总的哈希表下面,有若干个子哈希表。

  它的核心属性:

  其中,Segment是它的一个内部类,主要组成如下:

static final class Segment<K,V> extends ReentrantLock implements Serializable {private static final long serialVersionUID = 2249069246763182397L;// 和 HashMap 中的 HashEntry 作用一样,真正存放数据的桶transient volatile HashEntry<K,V>[] table;transient int count;transient int modCount;transient int threshold;final float loadFactor;// ...
}

  HashEntry也是一个内部类,主要组成如下:

  和 HashMap 的 Entry 基本一样,唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。

  这里不介绍构造函数。

3、Put 操作

  查看源码:

  首先是通过 key 定位到 Segment,之后在对应的 Segment 中进行具体的 put。

  虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。

  首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁。

  1、尝试自旋获取锁。
  2、如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取成功。

  1、加锁操作;
  2、遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
  3、为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
  4、释放锁;

4、Get 操作

  源码:
  Get 操作比较简单:
  1、Key 通过 Hash 之后定位到具体的 Segment;
  2、再通过一次 Hash 定位到具体的元素上;
  3、由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。

  ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁。

5、高并发线程安全

  Put 操作时,锁的是某个 Segment,其他线程对其他 Segment 的读写操作均不影响。因此解决了线程安全问题。

6、JDK8 的改进

  以上介绍的是 JDK8 以前的情况,到了 JDK8 则有了一些改进。

6.1 结构改变

  首先是结构上的变化,和 HashMap 一样,数组+链表改为数组+链表+红黑树。

6.2 HashEntry 改为 Node


  和 JDK7 的 HasEntry 作用相同,对 val 和 next 都用了 volatile 关键字,保证了可见性。

6.3 Put 操作的变化


  1、根据 key 计算出 hashcode,然后开始遍历 table;
  2、判断是否需要初始化;
  3、f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
  4、如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
  5、如果都不满足,则利用 synchronized 锁写入数据。
  7、如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。

6.4 Get 操作的变化


  根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
  如果是红黑树那就按照树的方式获取值。
  都不满足那就按照链表的方式遍历获取值。

6.5 总结

  1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。

深入浅出ConcurrentHashMap详解相关推荐

  1. [深入浅出Cocoa]详解键值观察(KVO)及其实现机理

    [深入浅出Cocoa]详解键值观察(KVO)及其实现机理 本文转载至    罗朝辉 (http://www.cppblog.com/kesalin/) 本文遵循"署名-非商业用途-保持一致& ...

  2. 深入浅出,详解深度优先搜索(DFS)

    深度优先搜索 如果把深度优先搜索比作一个人的话,那么这个人是一个执着的人,甚至倔强,不把一条路走到底不会返回(回溯),虽然执着倔强,但是他不傻,如果在探索的途中发现这条路走下去没有希望他会提前返回(剪 ...

  3. 深入浅出 万字详解 MyBatis看这一篇就够了!

    三层架构 界面层 Controller -> SpringMVC User interface layer,表示层,视图层,接受用户数据显示请求结果.使用web页面和用户交互,如jsp.html ...

  4. Java8 ConcurrentHashMap详解

    点个赞,看一看,好习惯!本文 GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了 3 个月总结的一线大厂 Java 面试总结,本 ...

  5. Java7 ConcurrentHashMap详解

    文章推荐 精选java等全套学习资源 精选java电子图书资源 精选大数据学习资源 java项目练习精选 ###Java7 ConcurrentHashMap ConcurrentHashMap 和 ...

  6. Java集合篇:ConcurrentHashMap详解(JDK1.8)

    (本文使用的源码都是基于JDK1.8的) (基于JDK1.6的版本可以参考这篇文章:https://blog.csdn.net/a745233700/article/details/83120464) ...

  7. Java集合篇:ConcurrentHashMap详解(JDK1.6)

    (本文有关ConcurrentHashMap的源码都是基于JDK1.6的) (基于JDK1.8的版本可以参考这篇文章:https://blog.csdn.net/a745233700/article/ ...

  8. Java集合:ConcurrentHashMap详解

    前言 近期深入学习了ConcurrentHashMap,便整理成一篇博文记录一下,请注意:此博文针对的是JDK1.6,因此如果你看到的源码跟我文中的不同,则可能是由于版本不一样. Concurrent ...

  9. 史上最详细的ConcurrentHashMap详解--源码分析

    ps.本文所有源码都是基于jdk1.6 首先说明一点,ConcurrentHashMap并不是可以完全替换Hashtable的,因为ConcurrentHashMap的get.clear函数是弱一致的 ...

最新文章

  1. linux redis 删除_Redis-安装amp;删除【Linux 版】
  2. Halcon初学者知识:用set_paint直观显示图像的属性
  3. java mongo subtract_春数据MongoDB的聚合框架,例外
  4. c#语言 修改xml文件路径,C#对XML操作:编辑XML文件内容-.NET教程,C#语言
  5. 【CodeForces - 689D】Friends and Subsequences(RMQ,二分 或单调队列)
  6. python安卓版开发环境搭建_React Native Android 开发环境搭建(Windows 版)
  7. python treeview显示多列_Python Gtk TreeView列数据显示
  8. 推荐3款简约好用录屏工具
  9. DORADO展现中间件的实现
  10. Monkey学习笔记三:Monkey脚本编写
  11. 石油化工行业的MES系统解决方案
  12. PHP判断浏览器类型及版本
  13. 华为云计算hcip证书有效期_华为云计算容器HCIP V1.0认证
  14. Pycharm工具下的数据可视化(图形绘制)
  15. 关于monitor模式
  16. Modbus功能码及错误码说明
  17. BMapGL实现地图轨迹运动(地图视角不变)
  18. J2EE开发实战基础系列一 HelloWorld
  19. 分治、CDQ分治小结(need to be updated)
  20. 乱贴小广告违法 有人仍一意孤行

热门文章

  1. 如何隐藏网络计算机,电脑隐藏wifi怎么设置_电脑如何设置隐藏wifi?-192路由网...
  2. [SSD安装3] 联想笔记本如何安装固态硬盘(全流程+装系统)
  3. Markdown笔记的搭建:vscode+印象笔记+PicGo
  4. 苹果手机nfc怎么复制门禁卡
  5. 基于自注意力机制与无锚点的仔猪姿态识别(农业工程学报)
  6. 送5本新出Spring Boot书籍!
  7. 信息论 输入概率的哈夫曼编码 C语言
  8. 架构设计必备:时序图说明及画法
  9. vscode返回上一步
  10. Android逆向之旅---爆破开启快手App的长视频拍摄权限功能