HashMap线程不安全|Hashtable|ConcurrentHashMap

文章目录

    • 常见集合线程安全性
    • HashMap为什么线程不安全?
      • 怎么保证HashMap线程安全
    • Hashtable
    • ConcurrentHashMap 引入细粒度锁
      • 代码中分析
      • 总结
    • 小结

常见集合线程安全性

ArrayList、LinkedList、TreeSet、HashSet、HashMap、TreeMap等都是线程不安全的。

HashTable是线程安全的。

HashMap为什么线程不安全?

来看个例子

public static void main(String[] args) {HashMap<String, Integer> map = new HashMap<>();// 创建两个线程同时向HashMap中添加1000个元素Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {map.put(String.valueOf(i), i);}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 1000; i < 2000; i++) {map.put(String.valueOf(i), i);}}});// 启动线程thread1.start();thread2.start();try {// 等待两个线程执行完成thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}// 输出 HashMap 的大小System.out.println("map集合大小: " + map.size());}//输出结果:map集合大小:1920(这个数字在变动)

在多线程环境下,如果多个线程同时HashMap 进行修改操作(例如添加、删除元素),可能会导致数据结构破坏,进而引发各种问题,比如丢失数据等。

怎么保证HashMap线程安全

第一
如何保证HashMap的线程安全呢?可能你们想到了synchronized ,确实,你可以通过在添加元素时使用 synchronized 来确保 HashMap 的线程安全性。这样做可以在多线程环境下保证对 HashMap 的操作是互斥的,从而避免了多个线程同时修改 HashMap 导致的线程不安全问题。

 Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {//synchronized (map) 中的 map 是一个对象锁//它指定了在执行同步代码块时使用的锁对象synchronized (map) {for (int i = 0; i < 1000; i++) {map.put(String.valueOf(i), i);}}}});

当一个线程进入同步代码块(即 synchronized (map) 所包围的部分)时,它会尝试获取 map 对象的锁。如果这个锁当前没有被其他线程占用,那么该线程将获得锁,并可以执行同步代码块中的操作如果该锁已经被其他线程占用,那么该线程将被阻塞,直到锁被释放。被锁住的对象将会在同步代码块执行完毕后自动释放。

第二
使用Collections.synchronizedMap() 包装:
Map<Integer, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());
这样可以获得一个线程安全的 HashMap,但性能可能不如 ConcurrentHashMap。

第三:
使用 ConcurrentHashMap:
ConcurrentHashMap 是专门为高并发环境设计的,JDK 1.8它使用了 CAS + synchronized 来保证线程安全性,而且性能表现优秀。
Map<Integer, String> concurrentHashMap = new ConcurrentHashMap<>();


Hashtable

Hashtable 使用的是全表加锁的方式来保证线程安全,也就是说,当一个线程要对 Hashtable 进行读写操作时,它会对整个 Hashtable 加锁,阻塞其他所有线程的访问
在这里插入图片描述
HashTable 替代品 ConcurrentHashMap ,ConcurrentHashMap 引入了细粒度锁和无锁读取等技术,大大提高了并发环境下的性能和扩展性


ConcurrentHashMap 引入细粒度锁

ConcurrentHashMap 之所以是线程安全的,主要是因为它在内部实现时采用了特殊的机制来确保多个线程同时访问和修改数据时不会发生冲突。

JDK 1.7 版本中的实现:
ConcurrentHashMap 在 JDK 1.7 中使用了分段锁(Segmentation)的结构,将整个哈希表分成了多个段(Segment),每个段有自己的锁。不同段之间的修改操作可以并发进行(提高了并发性能),只有在同一段内的操作才需要竞争锁。

JDK 1.8 版本中的优化:
JDK 1.8 对 ConcurrentHashMap 进行了重大优化,废弃了分段锁的设计,而是采用了更细粒度的锁分离技术。
在 JDK 1.8 中,ConcurrentHashMap 内部使用了基于 CAS(Compare and Swap 是一种原子操作,用于在多线程环境下实现对共享数据的安全更新。CAS 是一种乐观锁机制,可以避免使用传统的互斥锁,提高了并发性能。) 操作的 synchronized 关键字
同时,ConcurrentHashMap 在 JDK 1.8 中引入了红黑树作为链表的替代结构,当链表长度达到一定阈值时,会将链表转换为红黑树,以提高查找效率。这种优化又提高了 ConcurrentHashMap 的并发性能和吞吐量。

代码中分析

来看看put方法

    public static  void main(String[] args) {ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();map.put("a",111);}

在 JDK8 中,ConcurrentHashMap 的实现方式已经改变,不再采用分段锁的方式,而是采用了 CAS+Synchronized 的方式来保证线程安全

我们需要先了解tabAt ,casTabAt(本质是CAS 算法,看下面源码可知)的利用来保障线程安全的操作:
tabAt 方法用于从哈希表数组 tab 中获取指定索引 i 处的节点数组。
casTabAt 方法用于原子性地将哈希表数组 tab 中指定索引 i 处的值从 c 更新为 v。(CAS 比较并交换,可实现原子操作)

put源码分析:

  1. 遍历桶(Bin)时不加锁
    首先,代码会根据 hash 值找到对应的桶(tab[i]),并且如果该桶是空的(f == null),会尝试通过 CAS(compare-and-swap) 操作将新的节点放入其中,这一部分操作是无锁的。

    else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))break;
    }
    
  2. 对非空桶的头节点加锁
    当桶 tab[i] 已经存在节点时,会进入 synchronized (f) 代码块,其中 f 是桶中的头节点。通过对头节点 f 加锁,保证对该桶中的修改是线程安全的。

    synchronized (f) {if (tabAt(tab, i) == f) {// 锁定成功后,进行进一步操作}
    }
    
    • 这种加锁方式是“桶级别”的锁,而不是对整个 HashMapConcurrentHashMap 加全局锁,因此不会影响其他线程对其他桶的访问。

在这里插入图片描述

  1. 对链表或树结构的操作
    加锁后,代码会对链表中的节点进行遍历,或者在红黑树中进行查找(如果桶已经转化为树形结构)。在这个过程中,同步块只对桶的头节点加锁,而不是对每个节点加锁。因此,在修改链表或树的过程中,保证了线程安全性,但并不会影响其他线程对其他桶的操作。

    • 如果该桶是链表结构,会遍历链表并进行元素的插入操作:

      for (Node<K,V> e = f;; ++binCount) {K ek;if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K,V> pred = e;if ((e = e.next) == null) {pred.next = new Node<K,V>(hash, key, value, null);break;}
      }
      
    • 如果该桶是树形结构(红黑树),则调用 putTreeVal 方法进行树中的插入操作:

      if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}
      }
      
  2. 加锁粒度
    由于只对每个桶的头节点加锁,因此即使多个线程同时对不同的桶进行操作,也不会相互阻塞。只有在同一个桶中有多个线程试图修改数据时,才会发生锁竞争。这种设计提高了并发性能,相比全局锁的方式有更高的并发吞吐量。

  3. 树化操作
    当链表中的节点数量超过 TREEIFY_THRESHOLD 时,会将链表转化为红黑树以提高查询和修改效率。树化也是在线程安全的环境下进行的,但依然是通过对头节点加锁实现的。

    if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);
    

总结

  • 加锁机制:对每个桶(tab[i])的头节点进行加锁。这种锁定策略使得同一个桶中的修改是线程安全的。
  • 并发性能优化:这种设计避免了全局锁,提高了在高并发环境下的性能。当多个线程操作不同的桶时,操作不会相互干扰。

小结

而在 JDK 1.8 中,ConcurrentHashMap 放弃了分段锁,而是采用了更为精细的桶结构。每个桶可以独立加锁,使得并发修改操作可以更细粒度地进行。此外,当桶中的元素数量达到一定阈值时,链表结构会转变为红黑树,以减少搜索时间。这种锁分离技术提高了并发性能,使得 ConcurrentHashMap 在并发情况下表现更加出色。它是通过 CAS + synchronized 来实现线程安全的,并且它的锁粒度更小,查询性能也更高。


❤觉得有用的可以留个关注ya❤

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/54172.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言:数组

1. 数组的概念 数组是⼀组相同类型元素的集合&#xff1b;从这个概念中我们就可以发现2个有价值的信息&#xff1a; • 数组中存放的是1个或者多个数据&#xff0c;但是数组元素个数不能为0。 • 数组中存放的多个数据&#xff0c;类型是相同的。 数组分为⼀维数组和多维数组…

【C语言必学知识点七】你知道在动态内存管理中存在的内存泄露问题吗?遇到内存泄露时应该如何处理?今天跟你好好介绍一下如何正确使用calloc与realloc!!!

动态内存管理——动态函数&#xff08;calloc、realloc&#xff09;的使用 导读一、calloc函数1.1 函数介绍1.2 calloc的使用1.3 calloc与malloc 二、realloc函数2.1 函数介绍2.2 realloc的使用2.3 realloc的空间分配2.3.1 空间分配成功——地址的改变2.3.2 空间分配失败——内…

【在Linux世界中追寻伟大的One Piece】数据链路层

目录 1 -> 数据链路层 2 -> 对比理解“数据链路层”和“网络层” 3 -> 以太网 3.1 -> 以太网的帧格式 4 -> 认识MAC地址 4.1 -> 对比理解MAC地址和IP地址 5 -> 认识MTU 5.1 -> MTU对IP协议的影响 5.2 -> MTU对UDP协议的影响 5.3 -> MT…

UE5源码Windows编译、运行

官方文档 Welcome To Unreal Engine 5 Early Access Learn what to expect from the UE5 Early Access program. 链接如下&#xff1a;https://docs.unrealengine.com/5.0/en-US/Welcome/#gettingue5earlyaccessfromgithub Step 0&#xff1a;找到UE5源码 直接先上链接 https…

【Qt】Qml界面中嵌入C++ Widget窗口

1. 目的 qml做出的界面漂亮&#xff0c;但是执行效率低&#xff0c;一直想找一个方法实现qml中嵌入c界面。现在从网上找到一个方法&#xff0c;简单试了一下貌似可行&#xff0c;分享一下。 2. 显示效果 3. 代码 3.1 工程结构 3.2 pro文件 需要添加widgets > QT quick …

店群合一模式下的社区团购新发展——结合链动 2+1 模式、AI 智能名片与 S2B2C 商城小程序源码

摘要&#xff1a;本文探讨了店群合一的社区团购平台在当今商业环境中的重要性和优势。通过分析店群合一模式如何将互联网社群与线下终端紧密结合&#xff0c;阐述了链动 21 模式、AI 智能名片和 S2B2C 商城小程序源码在这一模式中的应用价值。这些创新元素的结合为社区团购带来…

设计模式重新整理

系统整理 河北王校长的 贯穿设计模式 和 王争的设计模式之美&#xff0c;希望能形成肌肉记忆 文章目录 为什么需要掌握设计模式1. 六大原则介绍1. 单一职责原则2. 开闭原则3. 里式替换原则4. 依赖倒置原则5. 接口隔离原则6. 迪米特法则 分类 单例模式适配器模式封装有缺陷的接口…

【可测试性实践】C++ 单元测试代码覆盖率统计入门

引言 最近在调研C工程怎么做单元测试和代码覆盖率统计&#xff0c;由于我们工程有使用Boost库&#xff0c;尝试使用Boost.Test来实现单元测试并通过Gcov和Lcov来生成代码覆盖率报告。本文记录完整的搭建测试Demo&#xff0c;希望能带来一定参考。 常用C单测框架对比 特性Goo…

嵌入式通信原理—SPI总线通信原理与应用

文章目录 SPI 简介基本原理工作模式特点 SPI寻址方式1. 片选&#xff08;Chip Select, CS&#xff09;2. 多从设备通信3. 菊花链&#xff08;Daisy-Chain&#xff09;模式4. 地址寄存器&#xff08;应用层&#xff09; SPI通信过程时钟信号生成&#xff08;SCLK&#xff09;数据…

9.15javaweb项目总结

1.贴吧界面算是完成了基本的 能通过url打开多个贴吧信息的界面了&#xff0c;界面水平不是很高&#xff0c;界面还有待提升&#xff0c;然后该界面的功能点还差点有点远&#xff0c;完成度不是很高。 2.解决了关注的功能问题 要考虑的地方有点多&#xff0c;最简单的就是点击…

Axure RP 9最新安装程序及汉化包下载(支持Win、Mac版,附下载安装教程)

数月前Axure RP官方已经发布了Axure RP 9的消息&#xff0c;并计划在今年夏天发布beta版本。新版Axure RP 9将是该工具向前迈出的重要一步&#xff0c;其中包括一系列广泛的改进&#xff1a;全面的UI修改&#xff0c;新的设计和文档功能以及前所未有的内部优化。我们已经彻底重…

【Python 数据分析学习】Pandas基础与应用(1)

题目 1 Pandas 简介1.1 主要特征1.2 Pandas 安装 2 Pandas中的数据结构2.1 Series 数据结构和操作2.1.1 Series的数据结构2.1.2 Seres的操作 2.2 DataFrame 数据结构和操作2.2.1 DataFrame 数据结构2.2.2 Dataframe 操作2.2.3 DateFrame 的特殊操作 2.3 Series 和 DataFrame 的…

Jenkins生成html报告

下载插件 1.需要下载插件 html Publisher plugins 2.下载Groovy(设置css样式&#xff09;&#xff0c;默认没有css样式 在Job配置页面&#xff0c;增加构建步骤Execute system Groovy script&#xff0c;在Groovy Command中输入上面命令&#xff0c;即可&#xff1a; System.…

鸡蛋检测系统源码分享

鸡蛋检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vision …

kafka之protobuf

Protobuf 的 .proto 文件是一种描述消息结构的定义文件&#xff0c;使用这种文件可以定义数据结构&#xff08;消息&#xff09;&#xff0c;然后生成对应语言的类或代码用于序列化和反序列化数据。生成 .proto 文件涉及到编写 .proto 文件定义&#xff0c;然后通过 protoc 编译…

理解Android开发中的MVC、MVVM和MVP设计模式

全篇大概1600 字&#xff0c;建议阅读时间10分钟。 引言 在 Android 应用开发中&#xff0c;设计模式是帮助开发者构建结构清晰、可维护性高的应用程序的关键工具。MVC&#xff08;Model-View-Controller&#xff09;、MVVM&#xff08;Model-View-ViewModel&#xff09;和 MV…

HarmonyOS开发5.0【封装request泛型方法】axios

一 准备工作 1. 先开启一下虚拟机的权限 src/main/module.json5 打开module.json5在15~19行 进行配置网络权限 2. 在终端下载安装一下 ohpm install ohos/axios 复制 粘贴进去回车就行 3. 这样显示就是安装好了 如果导入不行就关了重新启动 二 创建一个ETS文件&#xff0c;…

编译运行 webAssembly(wasm)

环境准备&#xff1a; lunix下docker 参考https://hub.docker.com/r/emscripten/emsdk 拉编译环境 docker pull emscripten/emsdk 编译 随便找个目录&#xff0c;敲下面命令&#xff0c;编译一个webAssembly 程序 # create helloworld.cpp cat << EOF > hellowo…

Python基础语法(1)上

常量和表达式 我们可以把 Python 当成一个计算器&#xff0c;来进行一些算术运算。 print(1 2 - 3) print(1 2 * 3) print(1 2 / 3) 这里我们可能会有疑问&#xff0c;为什么不是1.6666666666666667呢&#xff1f; 其实在编程中&#xff0c;一般没有“四舍五入”这样的规则…

Qt-QPushButton按钮类控件(22)

目录 描述 使用 给按钮添加图片 给按钮添加快捷键 添加槽函数 添加快捷键 添加组合键 开启鼠标的连发功能 描述 经过上面的一些介绍&#xff0c;我们也尝试的使用过了这个控件&#xff0c;接下来我们就要详细介绍这些比较重要的控件了 使用 给按钮添加图片 我们创建…