您的位置:首页 > 其它

HashMap源码分析系列 -- 第四弹:HashMap多线程解决方案

2020-09-05 15:38 891 查看

HashMap源码分析系列 – 第五弹:HashMap的线程安全问题解决方案

从今天起,争取一周之内整理完HashMap有关的各种问题。网上有很多HashMap的源码分析的帖子,我看了一些。当然也有写的非常好的,不过大多的源码分析帖企图一篇文章就把问题讲完,有时候看起来就很混乱。而且贴了较多的源码。这里我准备分为6个部分,分别上传五篇篇博文,希望用更清晰的方式,来总结HashMap的相关问题。

六个部分

前言

我们知道Jdk1.7有,HashMap链表死循环问题,属于严重的BUG。那么Jdk1.8采用头插法,不会出现链表死循环的问题,那么1.8就没有线程安全问题吗?

我们知道线程安全问题会出现在修改数据的情况下,比如put,remove方法,都会出现线程安全问题。

举个例子:

两个线程A、B都在进行put操作,并且hash函数计算出的插入下标是相同的,当线程A执行完第六行代码

if ((p = tab[i = (n - 1) & hash]) == null)
后由于时间片耗尽导致被挂起,而线程B得到时间片后在该下标处插入了元素,完成了正常的插入,然后线程A获得时间片,由于之前已经进行了hash碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B插入的数据被线程A覆盖了,从而线程不安全。

解决方案

使用并发环境安全的集合框架

Hashtable

public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
}

简单看一下源码

我们看到,Hashtable是用synchronized关键字保证线程安全的。

强化HashMap

使用Collections工具类的synchronizedMap方法,将HashMap改造为一个线程安全的Map。

Collections.synchronizedMap(new HashMap<String,String>());

简单看一下源码

public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}

我们看到这个方法无非就是原来的方法进一步包装,多了一个synchronized关键字,和Hashtable原理一样。

ConcurretnHashMap

在ConcurrentHashMap中有个重要的概念就是Segment。我们知道HashMap的结构是数组+链表形式,从图中我们可以看出其实每个segment就类似于一个HashMap。Segment包含一个HashEntry数组,数组中的每一个HashEntry既是一个键值对,也是一个链表的头节点。在ConcurrentHashMap中有2的N次方个Segment,共同保存在一个名为segments的数组当中。可以说,ConcurrentHashMap是一个二级哈希表。在一个总的哈希表下面,有若干个子哈希表。
为什么说ConcurrentHashMap的性能要比HashTable好,HashTables是用全局同步锁,而CconurrentHashMap采用的是锁分段,每一个Segment就好比一个自治区,读写操作高度自治,Segment之间互不干扰。

Case1:不同Segment的并发写入

不同Segment的写入是可以并发执行的。
Case2:同一Segment的一写一读

同一Segment的写和读是可以并发执行的。
Case3:同一Segment的并发写入

简单看一下源码

使用大量的volatile关键字来保证可见性

remove方法

在会出现线程问题的方法也是采用synchronized关键字保证线程安全的,只不过多了一些条件判断,用"智商"换时间。

put方法

使用安全的方式使用HashMap

读写分离

  • 没想好怎么写
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: