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源码分析:
-
遍历桶(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; }
-
对非空桶的头节点加锁:
当桶tab[i]
已经存在节点时,会进入synchronized (f)
代码块,其中f
是桶中的头节点。通过对头节点f
加锁,保证对该桶中的修改是线程安全的。synchronized (f) {if (tabAt(tab, i) == f) {// 锁定成功后,进行进一步操作} }
- 这种加锁方式是“桶级别”的锁,而不是对整个
HashMap
或ConcurrentHashMap
加全局锁,因此不会影响其他线程对其他桶的访问。
- 这种加锁方式是“桶级别”的锁,而不是对整个
-
对链表或树结构的操作:
加锁后,代码会对链表中的节点进行遍历,或者在红黑树中进行查找(如果桶已经转化为树形结构)。在这个过程中,同步块只对桶的头节点加锁,而不是对每个节点加锁。因此,在修改链表或树的过程中,保证了线程安全性,但并不会影响其他线程对其他桶的操作。-
如果该桶是链表结构,会遍历链表并进行元素的插入操作:
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;} }
-
-
加锁粒度:
由于只对每个桶的头节点加锁,因此即使多个线程同时对不同的桶进行操作,也不会相互阻塞。只有在同一个桶中有多个线程试图修改数据时,才会发生锁竞争。这种设计提高了并发性能,相比全局锁的方式有更高的并发吞吐量。 -
树化操作:
当链表中的节点数量超过TREEIFY_THRESHOLD
时,会将链表转化为红黑树以提高查询和修改效率。树化也是在线程安全的环境下进行的,但依然是通过对头节点加锁实现的。if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);
总结
- 加锁机制:对每个桶(
tab[i]
)的头节点
进行加锁。这种锁定策略使得同一个桶中的修改是线程安全的。 - 并发性能优化:这种设计避免了全局锁,提高了在高并发环境下的性能。当多个线程操作不同的桶时,操作不会相互干扰。
小结
而在 JDK 1.8 中,ConcurrentHashMap 放弃了分段锁,而是采用了更为精细的桶结构。每个桶可以独立加锁
,使得并发修改操作可以更细粒度地进行。此外,当桶中的元素数量达到一定阈值时,链表结构会转变为红黑树,以减少搜索时间。这种锁分离技术提高了并发性能,使得 ConcurrentHashMap 在并发情况下表现更加出色。它是通过 CAS + synchronized
来实现线程安全的,并且它的锁粒度更小
,查询性能也更高。
❤觉得有用的可以留个关注ya❤
相关文章:

HashMap线程不安全|Hashtable|ConcurrentHashMap
文章目录 常见集合线程安全性HashMap为什么线程不安全?怎么保证HashMap线程安全 HashtableConcurrentHashMap 引入细粒度锁代码中分析总结 小结 常见集合线程安全性 ArrayList、LinkedList、TreeSet、HashSet、HashMap、TreeMap等都是线程不安全的。 HashTable是线…...
01 会计概述
会计的定义:会计是以货币为计量单位,反映和监督一个单位经济活动的一种经济管理活动。会计的作用:就是提供决策信息、促使企业加强经营管理、考核管理层经济责任履行情况。会计人员职业道德:坚持诚信,守法奉公…...

开放式激光振镜运动控制器在Ubuntu+Qt下的文本标刻
开放式激光振镜运动控制器在UbuntuQt下的文本标刻 上节课程我们讲述了如何通过UbuntuQt进行振镜校正(详情点击→开放式激光振镜运动控制器在UbuntuQt下的激光振镜校正),本节文本标刻是在振镜校正的前提下实现的。 在正式学习之前࿰…...

推荐3款AIai论文大纲一键生成文献,精选整理!
在当前的学术写作环境中,AI论文大纲生成工具已经成为许多学者和学生的重要助手。这些工具不仅能够快速生成高质量的论文大纲,还能提供内容填充、文献引用和查重修改等全方位的服务。以下是三款值得推荐的AI论文大纲一键生成文献工具:千笔-AIP…...

数据库之索引<保姆级文章>
目录: 一. 什么是索引 二. 索引应该选择哪种数据结构 三. MySQL中的页 四. 索引分类及使用 一. 什么是索引: 1. MySQL的索引是⼀种数据结构,它可以帮助数据库高效地查询、更新数据表中的数据。 索引通过 ⼀定的规则排列数据表中的记录&#x…...

多维时序 | Matlab基于BO-LSSVM贝叶斯优化最小二乘支持向量机数据多变量时间序列预测
多维时序 | Matlab基于BO-LSSVM贝叶斯优化最小二乘支持向量机数据多变量时间序列预测 目录 多维时序 | Matlab基于BO-LSSVM贝叶斯优化最小二乘支持向量机数据多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于BO-LSSVM贝叶斯优化最小二乘支…...

Netty笔记03-组件Channel
文章目录 Channel概述Channel 的概念Channel 的主要功能Channel 的生命周期Channel 的状态Channel 的类型channel 的主要方法 ChannelFutureCloseFuture💡 netty异步提升的是什么要点总结 Channel概述 Channel 的概念 在 Netty 中,Channel 是一个非常重…...

1----安卓机型修复串码 开启端口 檫除基带 支持高通与MTK机型工具预览与操作解析
在玩机过程中。很多玩家会碰到各种各样的故障 。其中最多的就在于基带 串码类。由于目前的安卓机型必须修改或者写入串码等参数必须开启端口。而一些初级玩友不太了解开启参数端口的步骤。这个工具很简单的为安卓机型开启端口。并且操作相对简单。 此工具基本功能 1-----可以…...

Docker容器技术1——docker基本操作
Docker容器技术 随着云计算和微服务架构的普及,容器技术成为了软件开发、测试和部署过程中的重要组成部分。其中,Docker作为容器技术的代表之一,以其简便易用的特点赢得了广大开发者的青睐。 Docker允许开发者在轻量级、可移植的容器中打包和…...
ElasticSearch介绍+使用
ElasticSearch 1.背景 ElasticSearch的最明显的优势在于其分布式特性,能够扩展到上百台服务器,极大地提高了服务器的容错率。在大数据时代背景下,ElasticSearch与传统的数据库相比较,能够应对大规模的并发搜索请求,同…...

Redis——常用数据类型List
目录 List列表常用命令lpushlpushxrpushrpushlrangelpoprpoplindexlinsertllenlremltrim key start stoplset 阻塞版本命令blpopbrpop list的编码方式list的应用 List列表 Redis中的list相当于数组,或者 顺序表,一些常用的操作可以通过下面这张图来理解…...

前端基础知识+算法(一)
文章目录 算法二分查找条件注意方式基本原理左闭右闭正向写法 左闭右开正向写法 前端基础知识定时器及清除盒子垂直水平居中的方式垂直水平1.flex布局2.grid布局3.定位对于块级元素 解决高度塌陷的方式1.给父元素一个固定的高度2.给父元素添加属性 overflow: hidden;3.在子元素…...

photozoom classic 9解锁码2024年最新25位解锁码
photozoom classic 9 破解版顾及比恐龙还要稀有,我曾经和你一样一直再找,找了好几个月,也没有找到真的破解版,下载很多次, 都是病毒插件之类的 我昨天下了几次,没有一个不附带插件病毒木马的.......&#x…...

Oracle发邮件功能:设置的步骤与注意事项?
Oracle发邮件配置教程?如何实现Oracle发邮件功能? Oracle数据库作为企业级应用的核心,提供了内置的发邮件功能,使得数据库管理员和开发人员能够通过数据库直接发送邮件。AokSend将详细介绍如何设置Oracle发邮件功能。 Oracle发邮…...
优化理论及应用精解【9】
文章目录 二次型函数二次型函数详细解释一、定义二、性质三、应用四、示例五、图表辅助说明(由于文本限制,无法直接提供图表) “西尔维斯特准则”一、定义二、来源三、应用场景 参考文献 二次型函数 二次型函数详细解释 一、定义 二次型函…...

nginx实现https安全访问的详细配置过程
文章目录 前言什么是 HTTP?什么是 HTTPS?HTTP 和 HTTPS 的区别为什么 HTTPS 被称为安全的?配置过程配置自签名证书 前言 首先我们来简单了解一下什么是http和https以及他们的区别所在. 什么是 HTTP? HTTP,全称为“超…...
1. TypeScript基本语法
TypeScript 学习总结 TypeScript 是一种 JavaScript 的超集,增加了静态类型检查和编译时错误检测,从而提高了代码的可维护性和可靠性。以下是 TypeScript 的基础知识总结,包括语法、运算符、数据类型、变量声明和作用域。 ## 基本语法TypeS…...
C# UDP与TCP点发【速发速断】模式
1、UDP 客户端 //由于收发都在本机,所以只用一个IP地址 IPAddress addr IPAddress.Parse("127.0.0.1"); var ptLocal new IPEndPoint(addr,9001);//本机节点,用于发送var ptDst new IPEndPoint(addr,9002);//目标节点…...

pikachu下
CSRF(跨站请求伪造) CSRF(get) url变成了这样了,我们就可以新开个页面直接拿url去修改密码 http://pikachu-master/vul/csrf/csrfget/csrf_get_login.php?username1&password2&submitLogin CSRF(post) 这里只是请求的方式不同,…...

Go语言开发im-websocket服务和vue3+ts开发类似微信pc即时通讯
前言 IM即时通讯聊天, 为软件开发者打造,不依赖第三方sdk,完全用Go语言开发即时通讯服务,支持H5、Electron、Wails 、Uniapp和各种小程序的IM即时通讯, 快速实现私聊、群聊、在线客服!让你快速搭建一个微信聊天系统,打…...
将材质球中的纹理属性对应的贴图保存至本地
通过Texture2D的EncodeToPNG方法将纹理转为图片形式 material.GetTexture方法通过属性名获取纹理贴图 material.SetTexture方法通过属性名设置纹理贴图 属性名可在shader代码中查看 using UnityEngine; using System.IO;public class TextureSaver : MonoBehaviour {public…...

统信 UOS 服务器版离线部署 DeepSeek 攻略
日前,DeepSeek 系列模型因拥有“更低的成本、更强的性能、更好的体验”三大核心优势,在全球范围内备受瞩目。 本次,我们为大家提供了在统信 UOS 服务器版 V20(AMD64 或 ARM64 架构)上本地离线部署 DeepSeek-R1 模型的…...

欢乐熊大话蓝牙知识14:用 STM32 或 EFR32 实现 BLE 通信模块:从0到蓝牙,你也能搞!
🚀 用 STM32 或 EFR32 实现 BLE 通信模块:从0到蓝牙,你也能搞! “我能不能自己用 STM32 或 EFR32 实现一个 BLE 模块?” 答案当然是:能!还能很帅! 👨🏭 前…...
为何选择Spring框架学习设计模式与编码技巧?
📌 结论先行 推荐项目:Spring Framework 推荐理由:设计模式覆盖全面 编码技巧教科书级实现 Java 生态基石地位 🏆 三维度对比分析 维度SpringMyBatisXXL-JOB设计模式⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐代码抽象⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐生态价…...

EC2 实例详解:AWS 的云服务器怎么玩?☁️
弹性计算、灵活计费、全球可用,AWS EC2 全攻略 在 AWS 生态中,有两个核心服务是非常关键的,一个是 S3(对象存储),另一个就是我们今天的主角 —— Amazon EC2(Elastic Compute Cloud)…...

AutoGenTestCase - 借助AI大模型生成测试用例
想象一下,你正在为一个复杂的支付系统编写测试用例,需求文档堆积如山,边缘场景层出不穷,手动编写让你焦头烂额。现在,有了AutoGenTestCase,这个AI驱动的“测试用例生成机”可以从需求文档中自动生成数百个测…...

关于easyx头文件
一、窗口创建 (1)几种创建方式 #include<easyx.h>//easyx的头文件 #include<iostream> using namespace std;int main() {//创建一个500*500的窗口//参数为:长度,宽度,是否显示黑框(无参为不…...
MTK的Download agent是什么下载程序?
MTK(MediaTek)的Download Agent(DA)是一种与MTK设备进行通信的协议代理程序,在MTK设备的固件下载与烧录过程中起着关键作用,以下为你展开介绍: 下载原理 在MTK平台的固件下载过程中,DA会被加载到MTK设备的内部RAM中运行。它负责配置Flash及RAM的时序,从而建立起PC端…...

[特殊字符] Unity UI 性能优化终极指南 — ScrollRect篇
ScrollRect ManualScrollRect API 我参考了官方最新文档(基于UGUI 3.0包),加上实际性能测试经验,直接给你梳理: 🎯 Unity UI 性能优化终极指南 — ScrollRect篇 🧩 什么是 ScrollRectÿ…...

Spring AI Alibaba + Nacos 动态 MCP Server 代理方案
作者:刘宏宇,Spring AI Alibaba Contributor 文章概览 Spring AI Alibaba MCP 可基于 Nacos 提供的 MCP server registry 信息,建立一个中间代理层 Java 应用,将 Nacos 中注册的服务信息转换成 MCP 协议的服务器信息,…...