为什么要用ConcurrentHashMap?

ConcurrentHashMap是JUC包下的一个线程安全的HashMap类,我们都知道多线程的场景下要用ConcurrentHashMap来代替HashMap使用,有没有想过为什么不能用HashMap,为什么能用ConcurrentHashMap呢?下面我通过走源码的方式,带大家看一看其中的一些细节!

HashMap

map数组的一种,JDK1.8中的HashMap以数组+链表/红黑树的形式存在,这里不做过多解释

当我们在执行多线程任务时,若是操作的资源为HashMap类型时就可能会导致程序出现并发异常,如图

点到nextNode这个方法中去看源码,原因很明显,如图

那么,if判断中的两个变量是干什么的呢,为什么这俩变量不相同就要抛出一个异常?

首先,我们看一下我们从创建Hashmap变量到抛出异常这段时间内都做了些什么事情

整个流程的代码如下

public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
for (int i = 0;i < 20; i++){
new Thread(()-> {
map.put(Thread.currentThread().getName(),new Date().toString());
System.out.println(map);
},String.valueOf(i)).start();
}
}

我们点进HashMap的方法中的构造方法能看到,这里只给了loadFactor这个变量一个初始值,也就是我们熟知的加载因子0.75

我们再去看一下put方法都干了些什么

put->putVal

点开后发现,这不就是我们上面看到的if判断中的第一个变量吗?我们点到这个变量的定义的地方可以看到,这是一个int类型的且并未赋值

我们通过这点能得到一个初步的结论,当我们每执行一次put方法的时候,这个值就会进行一次+1的操作

大家可以看到下面还有一个叫afterNodeInsertion的方法,我们点开后发现是一个空方法,再看上边的注释,大概意思是给LinkedHashMap提供的回调的方法,LinkedHashMapHashMap的一个子类,我们不必理会

然后我们点开println方法

println->valueOf->toString

找到我们HashMap重写的toString方法,我们发现在HashMap中并没有找到被重写的toString方法,那么我们直接去搜一下他的父类

搜查过程中,如果用Ctrl+f搜索整个类,会看到有一条toString查询记录,此处为HashMap的内部类Node的方法,并非HashMap的

发现AbstractMap类中果然重写了toString方法

此处我们终于看到了迭代器,我们再返回HashMap类去看一下entrySet方法都干了啥

因为我们走过了put方法,所以此时HashMap中的entrySet是有内容的,可以看到这里是直接把entrySet返回了

entrySet实际上就是一个Set集合,将我们HashMap中的存储单元Entry放到了里面(HashMap.put)

然后我们通过泛型找到实现集合接口中迭代器方法的位置,又是一个HashMap的内部类,EntrySet

发现又一个没见过的类,我们顺势点开

点开nextNode方法后发现,哎,这不是报错的地儿吗,到现在我们modCount也有了,报错的节点也找到了,还差一个expectedModCount没找到呢,于是在一通检查后,突然想到,会不会在某个构造方法中,回去看了一下EntryIterator也没有自己写构造方法呀,于是打开了他的父类,一下子豁然开朗,直接看图吧

到这,这俩哥们总算找齐了,那么这里问题又来了,给expectedModCount赋的就是modCount的值呀,这俩哥们咋还能不一样呢

答案也浮出水面了,单线程下肯定是不会出问题,可是我们是多线程操作呀,如果A线程刚给完值,B线程跑putVal方法去了,跑完modCount+1了,然后A线程紧接着走到了nextNode方法中,一对比就不对了,然后就抛异常了

所以说HashMap在并发场景下还是很容易出问题的

ConcurrentHashMap

HashMap在多线程的场景下不能用了,不安全呀,于是适配多线程的线程安全的HashMap:ConcurrentHashMap应运而生

然后我们把原代码中的HashMap换成ConcurrentHashMap

public static void main(String[] args) {
ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
for (int i = 0;i < 20; i++){
new Thread(()-> {
map.put(Thread.currentThread().getName(),new Date().toString());
System.out.println(map);
},String.valueOf(i)).start();
}
}

执行了N遍,确实不报错了,每次循环都能打印成功,如此神奇的东西,让我们看看为什么他不报错了

首先点开这个类进去看看

发现这个类也继承了AbstractMap类,说明与HashMap是师出同门的呀,然后我们再去找上边那俩兄弟的时候发现,不见了,然后去找迭代器和next方法的时候发现,完全换了一套,所以自然不会像这样报错,那么他是怎么处理多线程操作的场景的呢

ConcurrentHashMap是通过synchronized + CAS 算法来实现线程安全的

如果去看源码的话,你会发现ConcurrentHashMap里面有很多Unsafe.compareAndSwap+数据类型的写法,这种写法就是利用CAS算法实现无锁化的修改值操作,此算法可以很大程度的减少加锁过程中造成的性能损耗

这个算法大概就是不断地去用内存中的变量值与代码预期的变量值是否相同,如果是一样的就会修改成功,如果不一样就会拒绝执行修改,用这种方式去判断当前线程中是否是最新的值,若不是则可能会覆盖其他线程的结果

正因此算法的判断方式,如果某个线程将值修改然后又改回去了,该算法仍然会认为这是最新值没有被改过

而通过观察源码发现,在操作Node相关对象时,会用synchronized将对象锁住

源码解析之为何要用ConcurrentHashMap的更多相关文章

  1. 源码解析之ConcurrentHashmap

    ConcurrentHashmap算是我看的集合源码里最难理解的了(当然ConcurrentLinkedList虽然代码少但理解起来也累),在Java1.8版本中DougLea大师巧通过妙地代码把锁粒 ...

  2. ConcurrentHashMap源码解析(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http:/ ...

  3. 第二章 ConcurrentHashMap源码解析

    注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http://www.cnblogs.com/java-zhao/p/5106189.html 1.对于 ...

  4. Java之ConcurrentHashMap源码解析

    ConcurrentHashMap源码解析 目录 ConcurrentHashMap源码解析 jdk8之前的实现原理 jdk8的实现原理 变量解释 初始化 初始化table put操作 hash算法 ...

  5. ConcurrentHashMap源码解析,多线程扩容

    前面一篇已经介绍过了 HashMap 的源码: HashMap源码解析.jdk7和8之后的区别.相关问题分析 HashMap并不是线程安全的,他就一个普通的容器,没有做相关的同步处理,因此线程不安全主 ...

  6. Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析

    目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...

  7. ConcurrentHashMap源码解析-Java7

    目录 一.ConcurrentHashMap的模型图 二.源码分析-类定义 2.1 极简ConcurrentHashMap定义 2.2 Segment内部类 2.3 HashEntry内部类 2.4 ...

  8. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  9. EventBus源码解析 源码阅读记录

    EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...

  10. JDK源码分析(12)之 ConcurrentHashMap 详解

    本文将主要讲述 JDK1.8 版本 的 ConcurrentHashMap,其内部结构和很多的哈希优化算法,都是和 JDK1.8 版本的 HashMap是一样的,所以在阅读本文之前,一定要先了解 Ha ...

随机推荐

  1. 13-flex

    01 flex2个重要的概念 02 flex布局模型 03 flex相关属性 04 flex container相关属性 4.1 flex direction 不同的值会改变主轴的方向 4.2 fle ...

  2. .NET Core WebApi接口ip限流实践

    .NET Core WebApi接口ip限流实践 前言 之前一直想实现接口限流,但一直没去实现,然后刚好看到一篇文章是基于AspNetCoreRateLimit 组件的限流策略.这个组件不做多的介绍, ...

  3. 实测952Mbps!四路千兆网PCIe拓展方案,国产工业级!

    测试环境说明 运行系统:Debian-11.8 评估板:TL3588-EVM(RK3588J) 模块:PCIe扩展2/4路千兆网口模块 方案:无锡沐创N500L-AM2C-DD.N500L-AM4C- ...

  4. makedown 笔记

    前言 记录一下自己经常忘的makedown指令,不断更新. makedown 添加空格  

  5. oracle 实现任务编码自增

    业务需求:任务编号前面4位数(通过查询其他表,值不确定),后面5位数实现自增 实现方法如下 1.创建序列 1 create sequence GENERAL_DES_TASK_SEQ_1 2 incr ...

  6. C# NPOI 读取Excel数据,附案例源码

    项目结构 注意:需要引入NPOI类库 C#代码 Form1.cs using NPOI.HSSF.UserModel; using NPOI.SS.UserModel; using System; u ...

  7. 解决方案 | vbnet的msgbox 窗口最前置,topmost属性设置

    For that you can use the TopMost Property of MsgBox (Number 262144) MsgBox("Hello there", ...

  8. 在Eclipse中使用Project Explorer视图与Package Explorer视图

    1.在项目文件比较多的情况下,为了方便查看整体和及时定位到项目文件,通常会同时使用Project Explorer视图与Package Explorer视图. 2.打开Project Explorer ...

  9. 【VMware VCF】VMware Cloud Foundation Part 03:准备 Excel 参数表。

    VMware Cloud Foundation 使用 VMware Cloud Builder 工具完成自动化以及标准化的部署,除了要准备必须的用于部署管理域并运行管理相关组件的 ESXi 主机以外, ...

  10. scratch编程作品-《滚动的物理小球》

    程序说明: <滚动的物理小球>是一款基于Scratch平台开发的小游戏.在这个游戏中,玩家通过按左右方向键来控制一个小球在屏幕上的左右移动.小球在移动过程中,完全遵循物理引擎的规则,如加速 ...