[Java]HashMap与ConcurrentHashMap的一些总结
HashMap与ConcurrentHashMap的一些总结
HashMap底层数据结构
JDK7
:数组+链表
JDK8
:数组+链表+红黑树
JDK8中的HashMap什么时候将链表转为红黑树?
当发现链表中的元素大于8之后,判断当前数组长度,如果数组长度小于64并不会马上转为红黑树,而是进行扩容。因为如果数组长度还比较小,就先利用扩容来缩小链表的长度。只有当链表中元素个数大于8,且数组长度大于64时才会将链表转为红黑树。
JDK8中的HashMap为什么要使用红黑树?
在HashMap
中,当元素的个数小于8时,链表的插入查询效率高于红黑树;当元素的个数大于8时,链表的插入查询效率小于红黑树。
JDK8中的HashMap的put方法的实现过程
- 根据
key
生成hashcode
- 判断当前
HashMap
对象中的数组是否为空,如果是空则对数组进行初始化 - 根据逻辑与运算,算出
hashcode
基于当前数组对应的数组下标i
- 判断数组中第
i
个位置的元素(以下称为tab[i]
)是否为空- 如果是空的,则将
key-value
值封装为Node
对象赋值给tab[i]
- 如果不是空的,判断
put
传入的key
是否等于tab[i].key
:- 如果等于那么说明存在相同
key
- 如果不等,判断
tab[i]
的类型:tab[i]
的类型为TreeNode
,表示第i
个位置上的是红黑树,那么就将key-value
值插入到红黑树中,并且在插入之前判断红黑树中是否存在相同key
tab[i]
的类型不是TreeNode
,表示第i个位置上的是链表,那么遍历链表查看是否有相同key
,并在遍历过程中对链表的节点数进行计数,当遍历到最后一个节点时将key-value
封装为Node
插入链表尾部,同时判断插入新节点之前的链表节点个数是否大于8,如果是,则将链表改为红黑树
- 如果等于那么说明存在相同
- 如果上述步骤发现存在相同
key
,则根据onlyIfAbsent
标记来判断是否需要更新value
值,然后返回oldValue
- 如果是空的,则将
modCount++
HashMap
的元素个数size
加1- 如果
size
大于扩容的阈值, 则进行扩容
JDK8中的HashMap的get方法实现过程
- 根据
key
生成hashcode
- 如果数组为空,则直接返回空
- 如果数组不为空,则利用
hashcode
和数组长度通过逻辑与操作算出key
所对应的数组下标i
- 如果数组的第
i
个位置上没有元素,则直接返回空 - 如果数组的第1个位上的元素的
key
等于get
方法所传进来的key
, 则返回该元素,并获取该元素的value
- 如果不等于则判断该元素还有没有下一个元素,如果没有,返回空
- 如果有则判断该元素的类型是链表结点还是红黑树结点
- 如果是链表则遍历链表
- 如果是红黑树则遍历红黑树
- 找到即返回元素,没找到的则返回空
JDK7和JDK8中的HashMap的不同点
- 8中使用了红黑树
- 7中的链表使用的是头插法,8中链表使用的是尾插法
- 7中的
Hash
算法比比8中的更复杂。Hash
算法更复杂生成的hashcode
更散列,查询性能更好,但是性能越低。8中因为使用了红黑树使性能得到了保障所以简化了Hash
算法。 - 扩容过程中7有可能对
key
重新进行哈希,但是8没有这个逻辑 - 8的扩容条件和7的扩容条件不同
- 8中多了一个
API
:putIfAbsent(key ,value)
- 7和8的扩容过程中转移元素的逻辑不同
JDK7中的ConcurrentHashMap是怎么保证并发安全
主要利用Unsafe
操作 + ReentrantLock
+分段思想。分段数越高则支持的最大并发量越高。
ConcurrentHashMap
的内部类Segment
就是用来表示某一个段的。
每个Segment
就是一个小型的HashMap
的, 当调用ConcurrentHashMap
的put
方法是,最终会调用到Segment
的put
方法,而Segment
类继承了ReentrantLock
,所以Segment
自带可重入锁,当调用到Segment
的put
方法时,会先利用可重入锁加锁, 加锁成功后再将待插入的key
,value
插入到小型HashMap
中,插入完成后解锁。
JDK7中的ConcurrentHashMap的底层原理
ConcurrentHashMap
底层是由两层嵌套数组来实现的:
ConcurrentHashMap
对象中有一个属性segments
, 类型为Segment
[];Segment
对象中有一个属性table
,类型为HashEntry[]
;当调用ConcurrentHashMap
的put
方法时,先根据key
计算出对应的Segment[]
的数组下标j
,确定好当前key
,value
应该插入到哪个Segment
对象中,如果segments[j]
为空,则利用自旋锁的方式在j
位置生成一个Segment
对象。然后调用Segment
对象的put
方法。Segment
对象的put
方法会先加锁, 然后也根据key
计算出对应的HashEntry[]
的数组下标i
,然后将key,value
封装为HashEntry
对象放入该位置,此过程和JDK7
的HashMap
的put
方法一样,然后解锁。在加锁的过程中逻辑比较复杂,先通过自旋加锁,如果超过一 定次数就会直接阻塞等等加锁。
JDK8中的ConcurrentHashMap是怎么保证并发安全
主要利用Unsafe
操作 + synchronized
关键字。
Unsafe
操作的使用仍然和JDK7
中的类似,主要负责并发安全的修改对象的属性或数组某个位置的值。
synchronized
主要负责在需要操作某个位置时进行加锁(该位置不为空),比如向某个位置的链表进行插入结点,向某个位置的红黑树插入结点。JDK8
中其实仍然有分段锁的思想,只不过JDK7
中段数是可以控制的,而JDK8
中是数组的每一个位置都有一把锁。
当向ConcurrentHashMap
中put
一个key,value
时:
- 首先根据
key
计算对应的数组下标i
,如果该位置没有元素,则通过自旋的方法去向该位置赋值。 - 如果该位置有元素,则
synchronized
会加锁 - 加锁成功之后, 在判断该元素的类型:
- 如果是链表节点则进行添加节点到链表中
- 如果是红黑树则添加节点到红黑树
- 添加成功后,判断是否需要进行树化
addCount
,这个方法的意思是ConcurrentHashMap
的元素个数加1,但是这个操作也是需要并发安全的,并且元素个数加1成功后,会继续判断是否要进行扩容,如果需要,则会进行扩容,所以这个方法很重要。- 同时一个线程在
put
时如果发现当前ConcurrentHashMap
正在进行扩容则会去帮助扩容。
JDK7和JDK8中的ConcurrentHashMap的不同点
- 首先是包括了
HashMap
中的不同点 JDK8
中没有分段锁了, 而是使用synchronized
来进行控制JDK8
中的扩容性能更高, 支持多线程同时扩容, 实际上JDK7
中也支持多线程扩容,因为JDK7
中的扩容是针对每个Segment
的,所以也可能多线程扩容, 但是性能没有JDK8
高, 因为JDK8
中对于任意一个线程都可以去帮助扩容JDK8
中的元素个数统计的实现也不一 样了,JDK8
中增加了CounterCell
来帮助计数,而JDK7
中没有,JDK7
中是put
的时候每个Segment
内部计数,统计的时候是遍历每个Segment
对象加锁统计。
[Java]HashMap与ConcurrentHashMap的一些总结的更多相关文章
- 轻松理解 Java HashMap 和 ConcurrentHashMap
前言 Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据. 本篇主要想讨论 ConcurrentHashMap 这样一个并发容器,在正式开始之前我觉得有必要谈谈 ...
- JAVA HashMap与ConcurrentHashMap
HashMap Fast-Fail(遍历时写入操作异常) 在使用迭代器的过程中如果HashMap被修改,那么ConcurrentModificationException将被抛出,也即Fast-fai ...
- [Java集合] 彻底搞懂HashMap,HashTable,ConcurrentHashMap之关联.
注: 今天看到的一篇讲hashMap,hashTable,concurrentHashMap很透彻的一篇文章, 感谢原作者的分享. 原文地址: http://blog.csdn.net/zhanger ...
- java基础知识再学习--HashMap与ConcurrentHashMap的区别
引用:http://blog.csdn.net/xuefeng0707/article/details/40834595 从JDK1.2起,就有了HashMap,正如前一篇文章所说,HashMap不是 ...
- Java中关于Map的使用(HashMap、ConcurrentHashMap)
在日常开发中Map可能是Java集合框架中最常用的一个类了,当我们常规使用HashMap时可能会经常看到以下这种代码: Map<Integer, String> hashMap = new ...
- 沉淀再出发:java中的HashMap、ConcurrentHashMap和Hashtable的认识
沉淀再出发:java中的HashMap.ConcurrentHashMap和Hashtable的认识 一.前言 很多知识在学习或者使用了之后总是会忘记的,但是如果把这些只是背后的原理理解了,并且记忆下 ...
- Java并发指南13:Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析
Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析 转自https://www.javadoop.com/post/hashmap#toc7 部分内容转自 http: ...
- java并发编程笔记(十)——HashMap与ConcurrentHashMap
java并发编程笔记(十)--HashMap与ConcurrentHashMap HashMap参数 有两个参数影响他的性能 初始容量(默认为16) 加载因子(默认是0.75) HashMap寻址方式 ...
- Java中HashMap与ConcurrentHashMap的区别
从JDK1.2起,就有了HashMap,正如前一篇文章所说,HashMap不是线程安全的,因此多线程操作时需要格外小心. 在JDK1.5中,伟大的Doug Lea给我们带来了concurrent包,从 ...
- JAVA学习:HashMap 和 ConcurrentHashMap
一.最基本的HashMap 和 ConcurrentHashMap 1.HashMap的结构和底层原理:由数组和链表组成,数组里面每个地方都存了Key-Value这样的实例,在Java7叫Entry ...
随机推荐
- flexible+rem移动端适配
- C#设计模式06——适配器的写法
什么是适配器模式? 适配器模式是一种结构型设计模式,用于将现有接口转换为符合客户端期望的接口.适配器模式允许不兼容的类可以相互协作. 为什么需要适配器模式? 在实际开发中,经常会遇到需要复用一些已有的 ...
- 接口自动化复习第四天利用正则和faker提取替换变量值
在做接口自动化测试的时候,我们经常会遇到,有些字段利用随机生成数据就行了,不需要自己去构造测试数据.今天我就是要python中的第三方库faker来构造随机数,其次使用正则表达式来提取变量. 首先在接 ...
- appium(二)安装(Android)
一.安装Appium-desktop 1.官网下载安装包: http://appium.io/
- Angular系列教程之单向绑定与双向绑定
.markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...
- 基于AHB_BUS Clac slave详解
基于AHB-APB BUS slave详解 1.目录 高内聚:让模块的功能更集中,更单一. AMBA总线例子,需要有一个模块和AMBA进行交互,就可以单独将与AHB总线进行交互的部分作为一个模块.经常 ...
- Vue2 - 配置跨域
在根目录下创建 vue.config.js 文件 . 即可 vue.config.js : // vue.config.js 配置说明 //官方vue.config.js 参考文档 https://c ...
- Shell-获取终端输入-read
- TCP连接状态的多种判断方法
前言 在TCP网络编程模型中,无论是客户端还是服务端,在网络编程的过程中都需要判断连接的对方网络状态是否正常.在linux系统中,有很多种方式可以判断连接的对方网络是否已经断开. 通过错误码和信号 ...
- 【转帖】Linux查看二进制文件:一招制敌(linux二进制查看文件)
https://www.dbs724.com/146055.html 一招制敌:学会Linux查看二进制文件 在Linux操作系统中,二进制文件是一种常见的文件类型.如果你想深入了解一个二进制文件,可 ...