你可知HashMap为什么是线程不安全的
作者:桐花思雨 发布时间:2021-12-10 20:14:59
HashMap 的线程不安全
HashMap 的线程不安全主要体现在下面两个方面
在 jdk 1.7 中,当并发执行扩容操作时会造成环形链和数据丢失的情况
在 jdk 1.8 中,在并发执行 put 操作时会发生数据覆盖的情况
对于 jdk 1.7 中 HashMap 的线程不安全,暂且不谈了,我们主要看看 jdk 1.8 中的
HashMap 中的 put() 方法
该 put() 方法是 jdk 1.8 中的
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 判断 table[] 是否为空,如果是空的就创建一个 table[],并获取他的长度n
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 如果单链表节点 Node<K,V> p == tab[i = (n - 1) & hash]) == null,
// 就直接 put 进单链表中,说明此时并没有发生 Hash 冲突
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
// 说明索引位置已经放入过数据了,已经在单链表处产生了Hash冲突
Node<K,V> e; K k;
// 判断 put 的数据和之前的数据是否重复
if (p.hash == hash &&
// 进行 key 的 hash 值和 key 的 equals() 和 == 比较,如果都相等,则初始化数组 Node<K,V> e
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 判断是否是红黑树,如果是红黑树就直接插入树中
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
// 如果不是红黑树,就遍历每个节点,判断单链表长度是否大于等于 7,
// 如果单链表长度大于等于 7,数组的长度小于 64 时,会优先选择扩容
// 如果单链表长度大于等于 7,数组的长度大于 64 时,才会选择单链表--->红黑树
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
// 采用尾插法,在单链表中插入数据
p.next = newNode(hash, key, value, null);
// 如果 binCount >= 8 - 1
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
// 判断索引每个元素的key是否可要插入的key相同,如果相同就直接覆盖
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
// 说明数组或者单链表中有相同的key,因此只需要将value覆盖,并将oldValue返回即可
if (e != null) {
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// 说明没有key相同,因此要插入一个key-value,并记录内部结构变化次数
++modCount;
// 判断是否扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
数据的覆盖一
第 13 行代码是判断是否出现 hash 冲突的,假设两个线程 A、B 都在进行 put 操作,并且它们 put 数据的 key 的 hash 值是相同的,同时它们 keyA == keyB 为 true 或者 keyA.equals(keyB) 为 true,也就是说它们 put 数据的 value 是不相同的
当线程 A 执行完第 13 行代码后由于时间片耗尽导致被挂起,而线程 B 得到时间片后在该单链表处插入了元素,完成了正常的插入
然后线程 A 获得时间片,由于之前已经进行了 hash 冲突的判断,所有此时不会再进行判断,而是直接进行插入覆盖,这就导致了线程 B 插入的数据被线程 A 覆盖了,从而发生了线程不安全
数据的覆盖二
第 58 行处有个 ++size,我们这样想,还是线程 A、B,这两个线程同时进行 put 操作时,假设当前 HashMap 的 size 大小为 10
当线程 A 执行到第 58 行代码时,从主内存中获得 size 的值为 10 后准备进行 +1 操作,但是由于时间片耗尽只好让出 CPU
于是线程 B 得到 CPU 调度,还是从主内存中拿到 size 的值 10 进行 +1 操作,完成了 put 操作,并将 size = 11 写回了主内存
然后线程 A 再次得到 CPU 调度,并继续执行(此时 size 的值仍为10),当执行完 put 操作后,还是将 size = 11 写了回内存。
此时,线程 A、B 都执行了一次 put 操作,但是 size 的值只增加了 1,所有说还是由于数据覆盖又导致了线程不安全
// HashMap 中 size 变量
transient int size;
来源:https://blog.csdn.net/weixin_38192427/article/details/122591110


猜你喜欢
- 采取的方法是Fragment+FragmentTabHost组件来实现这种常见的app主页面的效果首先给出main.xml文件
- 前言这篇文章探索一下springboot的tomcat是如何实现的一、起步依赖1.首先我们了解一下起步依赖SpringBoot结合Maven
- 简介单例指的是只能存在一个实例的类(在C#中,更准确的说法是在每个AppDomain之中只能存在一个实例的类,它是软件工程中使用最多的几种模
- java * 类可以分为两种。 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存
- POM:<dependency> <groupId>com.baomidou</groupId&g
- 项目中用到WebView加上进度条放在顶部,让用户知道加载进度情况,可以提高用户体验:效果:布局:<RelativeLayoutand
- 一、Java类的加载顺序引用1个网上的经典例子,并做稍许改动,以便大家更好地理解。public class Animal {
- 前言最近在用 MVP + RxJava + Retrofit 写项目,觉得相对于其他的开发框架,这的确是给我们带来了很多方便,但是在网上搜寻
- Android Init进程对信号的处理流程在Android中,当一个进程退出(exit())时,会向它的父进程发送一个SIGC
- 前言在单机应用时代,我们对一个共享的对象进行多线程访问的时候,使用java的synchronized关键字或者ReentrantLock类对
- 昨天在与对端系统调接口的时候,对端系统对我们传过去的json串老是处理不了,后来查原因是应为我们传过去的json串里有json对象数组,因为
- 因为mybatis好使,所以几乎需要操作数据库的时候,我都会使用mybatis,而且在一个正式的项目中,同时存在BS和CS的程序,都使用的M
- 前言我们经常会被问到这么一个问题:SpringBoot相对于spring有哪些优势呢?其中有一条答案就是SpringBoot自动注入。那么自
- 上一节初步了解了Android端的贝塞尔曲线,这一节就举个栗子练习一下,仿QQ未读消息气泡,是最经典的练习贝塞尔曲线的东东,效果如下附上gi
- 本文实例讲述了C#实现在前端网页弹出警告对话框(alert)的方法。分享给大家供大家参考。具体如下:通常我们通过JS生成警告对话框,下面的代
- 基本环境:Android studio3.6NDK:r15c(尽量使用该版本)Opencv3.4.1 android sdk操作:(1)新建
- DataBindings属性是很多控件都有的属性,作用有2方面。一方面是用于与数据库的数据进行绑定,进行数据显示。另一方面用于与控件或类的对
- SpringBoot读取外置logback配置文件springboot项目可以读取外置配置文件,避免了修改配置文件需要重新打包部署的问题。部
- 一、@RequestMapping注解的功能从注解名称上我们可以看到,@RequestMapping注解的作用就是将请求和处理请求的控制器方
- 1、下载SpringMVC框架架包,下载地址: 点击下载 点击打开地址如图所示,点击下载即可 然后把相关的jar复制到lib下导