Hashtable vs ConcurrentHashMap以及底层、特点分析

本文详细对比了Hashtable和ConcurrentHashMap的特性,包括它们的线程安全性、并发度、扩容机制以及在不同版本的实现差异。重点介绍了1.8之前的Segment结构和1.8之后的锁定位策略,同时还展示了Hashtable的扩容规则和良好的哈希分散性。对于Java 7和8的ConcurrentHashMap,分析了其内部结构、扩容策略以及并发put的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Hashtable vs ConcurrentHashMap总体比较

1.Hashtable 与 ConcurrentHashMap 都是线程安全的 Map 集合

2.Hashtable 并发度低,整个 Hashtable 对应一把锁,同一时刻,只能有一个线程操作它

3.  1.8 之前 ConcurrentHashMap 使用了 Segment + 数组 + 链表的结构,每个 Segment 对应一把锁,如果多个线程访问不同的 Segment,则不会冲突

        ①演示并发度

        ②演示 Segment 索引计算

        ③演示扩容

        ④演示 Segment[0] 原型

4.  1.8 开始 ConcurrentHashMap 将数组的每个头节点作为锁,如果多个线程访问的头节点不同,则不会冲突。首次生成头节点时如果发生竞争,利用 cas 而非 syncronized,进一步提升性能

        ①比较 7 的 ConcurrentHashMap

        ②理解 capacity 和 factor

        ③演示并发 put

        ④演示扩容,说明三个问题 forwardingNode ,扩容时的 get,扩容时的 put

        ⑤关于超过树化阈值时的扩容问题,如果容量已经是 64,直接树化,否则在原来容量基础上做 3 轮扩容

二、Hashtable特点和演示

Hashtable初始容量为11,每次扩容是上一次容量*2+1

放入1,2,3,4,5,6,7,8 

1e9a6f86b5a6414cb2ad74c91641523f.png

在放入9就出现扩容

a9861e229bf64383ba25333929848179.png

 他的hashCode计算不需要经历二次hash,因为他的容量是质数,有比较好的hash分散性,我们看看源码,hash按位与,为了保证正数取余 

2d6cc348d1e64bac99731c989d528290.png

 三、Java7版本的ConcurrentHashMap特点和演示

1、并发度

        蓝色外层的大数组就是Segment数组,Segment数组里面套一个小数组HashEntry,他等价我们普通hashmap的结构,如果出现索引冲突,他会构成一个链表。 

7ab4276a8f23494586329129ad7d760b.png

 将并发度改成8,数组长度随着变化,

88b39daee7c2494294f5f41fb800c16a.png

        那capacity容量是决定小数组的长度,小数组长度计算是容量除以并发度,如果容量没有并发度大,他就会以最小值2创建。

6ec411f7da1449aabd0a319c5259ce79.png

 2、Segment索引计算

 它是需要二次hash,也就是一些移位,异或的操作,使得hash分布更加均匀,我们已下图为例,二次hash如何变成segment下标。af5fd9e45d444c1d869ca168b07eab55.png

将二次hash值输入计算器,本次的并发度为16,2的4次方,所以看他的高4位,则为1100,十进制为12

7b3cd47980ff4899954f32549a6f3ed2.png

那我们再看看小数组的计算方式

c308a945cd44434d8a0b8aaf0cafc878.png

小数组大小为2的1次方,看他的低一位,也就是0

1ee0deccd4844cf78b3971c575720938.png

3、演示扩容

外层segment是不会扩容,会变化的是里面的小数组,当元素个数超过3/4会触发扩容,每次翻倍

值得注意的是,他扩容只会扩容当前索引下的小数组,其他的不会扩容

1ae86c614b9c49f995440615b11612c4.png

4、演示segment[0]原型

创建时,发现除了0其他索引没有小数组,那0索引的小数组作用是什么,它是为了作为原型,其他小数组创建将以这个为原型

e59a3eabdeb6468ba977cc54de8f471f.png

75a2f7554c1346c6af880f3ca88c3e76.png

看下图,如果这个时候我们再创建一个新的小数组 

e9011a85593442ac9689251ac420ed78.png

 他就会以segment[0]为原型cc90edda5ec34e52ab0fe952f36f29e3.png

四、Java8版本的ConcurrentHashMap特点和演示

1、比较7的ConcurrentHashMap

8版本的特点是不再采用小数组模式,他直接是数组加链表模式,链表过长也会转成红黑树。初始化的时机。7调用构造方法后,整个数据结构都会创建完成,但8版本调用之后,不会创建,属于懒汉式初始化。 

cf610d9ec6f44214bebcdf0eb57531da.png

 元素个数超过容量3/4触发扩容,hash值重新计算dfebbef43ceb42ddae96bf865cf26bf7.png

2、理解capacity和factor 

        为什么创建出来长度为32,8版本的capacity含义是将来我要放16个元素,所以要创建一个数组长度放下16个元素,但不得超过表容量的3/4,而且必须为2的n次方。

c9248ceb3a9e42a9ac10ecb5ebfd5d6c.png

8版本的扩容因子只会在一开始创建时用于计算表长度,其余时间不会用到它。看下图11个元素还没扩容,是固定的3/4才会出现扩容。 

b29a15fd249d4501b4d6f37bf25aa301.png

3、并发put

8版本里面它是把锁加载每个链表头上,比如下图a处于链表头,之后来的每一个线程都会给a先上锁,如果有其他线程,则会被阻塞,特意设置abc固定hash,b插入时暂停10s,这10s区间c无法完成插入操作的,这个锁只针对这个索引链表头,其他索引不影响。

c79b6fc705324211be5241c5c5706dd4.png

4、扩容

看一下扩容过程,他先会创建一个32长度表,就是把每个索引的链表迁移到新数组。 

f75a20637a3e45d09a75e160782e754e.png

首先他会从15号开始,处理完成就会将旧链表头替换成forwardingNode,表示这个处理完,其他线程来了就会知道这个已被处理。10号的元素重新计算hash,不变的迁移到原来位置,变得到新位置

36189b99816e4116a4b854f38319d796.png

所有节点完成,迁移工作也就完成了

fb33f2eee66741f9847b6fc476ff8e0a.png

 迁移细节问题:

扩容过程其他线程put,get怎么处理?

1、如果这时候来一个get操作,分两种情况,在0-13区间,直接在原表查找,14-15区间,查询线程到新的查找,它是怎么知道去新或旧查找,就通过这个链表头的状态。 

1de86ba7c32847a5977b46cc4f72777f.png

2、线程来了要进行一个查询,查询恰好是10索引链表数据,比如查询这个2,链表迁移工作此时未完成,应该在旧的找。但是旧的表1和新表1不是同一个对象,因为hash的重新计算,新的1的next是空,旧的next是2,不会找得到2。所以扩容线程在做链表在迁移时,一般创建一个新的链表对象,因为链表指向发生改变。当然ConcurrentHashMap有做优化,如果最后几个元素hash后相同,则不会创建新的对象。

f73bd4f420714e4082792eb91a5a2e57.png

3、put分三种情况:(1)put线程刚好put到了正在迁移的链表的前半部分0-9位置这时候能够并发执行(2)put线程刚好是10索引位置,那他会阻塞,因为扩容线程会对链表头加锁(3)put线程到11-15,他不能到新的表中put,新的表要等所有迁移完成才可以put,这时候put线程可以帮忙处理迁移行为,每个线程可以处理16个索引。

a03b5f3ccc6147a3aa2a209688bc8913.png

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悠哉iky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值