为什么要用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. Linux 内核:I2C子系统分析(1)基于子系统的驱动分析与实现

    Linux 内核:I2C子系统分析(1)基于子系统的驱动分析与实现 背景 在学习高通平台的有关知识,看到一篇博客中介绍了GPIO模拟I2C设备,觉得挺有意思的. 看了看有关的实现,发现自己之前学习从L ...

  2. 从零开始带你上手体验Sermant自定义插件开发

    本文分享自华为云社区<Sermant自定义插件开发上手体验>,作者:华为云开源. 一.研究缘由 由于目前我们所处的行业是汽车行业,项目上进行云服务的迁移时使用到了Sermant中的相关插件 ...

  3. .NET CORE 部署提示 An error occurred while starting the application.

    错误提示: 解决方法 检查一下nuget引用包 是否更新了版本,如果升级或者降级了版本,需要将新的dll文件更新一下

  4. 记一次centos7.9崩溃恢复操作(limits.conf配置失误),救援模式

    引起故障的原因:调整了操作系统的内核参数文件limits.conf,*  soft nproc 131072 *  hard nproc 131072 *  soft nofile 65536 *   ...

  5. tp6 uniapp跨越问题

    自己写一个简单的中间件

  6. 数据仓库建模工具之一——Hive学习第一天

    hive分布式搭建文档 谷歌浏览器下载网址:Google Chrome – Download the fast, secure browser from Google 华为云镜像站:https://m ...

  7. 基于Java+SpringBoot+Vue宠物咖啡馆平台设计和实现

    \n文末获取源码联系 感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询 系统介绍: 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成 ...

  8. [rCore学习笔记 05]第0章作业题

    作业1 略. 作业2 C语言程序 gcc编译 gcc -o main main.c 编译报错 成功产生异常 main.c: In function 'main': main.c:5:26: warni ...

  9. vue中使用xlsx导出excel文件

    俗话说,前人栽树,后人乘凉,感谢强人封装好的xlsx,直接使用就可以了.这是网上找到的,也不知道原作者是不是这位博主,先贴出来吧: https://www.cnblogs.com/boylxx/p/1 ...

  10. Javascript 转Date对象为字符串实现函数

    转Date对象为字符串实现函数 function formatDate(time, format = "Y-MM-dd HH:mm:ss") { /** 格式化字符说明 Y 年 四 ...