当前位置: 首页 > news >正文

ConcurrentHashMap是如何实现线程安全的

目录

原理:

初始化数据结构时的线程安全

 put 操作时的线程安全


原理:

        多段锁+cas+synchronize

初始化数据结构时的线程安全

在 JDK 1.8 中,初始化 ConcurrentHashMap 的时候这个 Node[] 数组是还未初始化的,会等到第一次 put() 方法调用时才初始化

final V putVal(K key, V value, boolean onlyIfAbsent) {if (key == null || value == null) throw new NullPointerException();int hash = spread(key.hashCode());int binCount = 0;for (Node<K,V>[] tab = table;;) {Node<K,V> f; int n, i, fh;// 判断Node数组为空if (tab == null || (n = tab.length) == 0)// 初始化Node数组tab = initTable();......
}

此时会有并发问题的,如果多个线程同时调用 initTable() 初始化 Node[] 数组怎么办?看看 Doug Lea 大师是如何处理的

private final Node<K,V>[] initTable() {Node<K,V>[] tab; int sc;// 每次循环都获取最新的Node[]数组引用while ((tab = table) == null || tab.length == 0) {// sizeCtl是一个标记位,若为-1,代表有线程在进行初始化工作了if ((sc = sizeCtl) < 0)// 让出CPU时间片Thread.yield(); // 此时,代表没有线程在进行初始化工作,CAS操作,将本实例的sizeCtl变量设置为-1	else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {// 如果CAS操作成功了,代表本线程将负责初始化工作try {// 再检查一遍数组是否为空if ((tab = table) == null || tab.length == 0) {// 在初始化ConcurrentHashMap时,sizeCtl代表数组大小,默认16// 所以此时n默认为16int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];// 将其赋值给table变量table = tab = nt;// 通过位运算,n减去n二进制右移2位,相当于乘以0.75// 例如16经过运算为12,与乘0.75一样,只不过位运算更快sc = n - (n >>> 2);}} finally {// 将计算后的sc(12)直接赋值给sizeCtl,表示达到12长度就扩容// 由于这里只会有一个线程在执行,直接赋值即可,没有线程安全问题,只需要保证可见性sizeCtl = sc;}break;}}return tab;
}

总结:就算有多个线程同时进行 put 操作,在初始化 Node[] 数组时,使用了 CAS 操作来决定到底是哪个线程有资格进行初始化,其他线程只能等待。用到的并发技巧如下

  • volatile 修饰 sizeCtl 变量:它是一个标记位,用来告诉其他线程这个坑位有没有线程在进行初始化工作,其线程间的可见性由 volatile 保证
  • CAS 操作:CAS 操作保证了设置 sizeCtl 标记位的原子性,保证了在多线程同时进行初始化 Node[] 数组时,只有一个线程能成功

 put 操作时的线程安全

public V put(K key, V value) {return putVal(key, value, false);
}final V putVal(K key, V value, boolean onlyIfAbsent) {// K,V 都不能为空if (key == null || value == null) throw new NullPointerException();// 取得 key 的 hash 值int hash = spread(key.hashCode());// 用来计算在这个节点总共有多少个元素,用来控制扩容或者转换为树int binCount = 0;// 数组的遍历,自旋插入结点,直到成功for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh;// 当Node[]数组为空时,进行初始化if (tab == null || (n = tab.length) == 0)    			tab = initTable();// Unsafe类volatile的方式取出hashCode散列后通过与运算得出的Node[]数组下标值对应的Node对象// 此时 Node 位置若为 null,则表示还没有线程在此 Node 位置进行插入操作,说明本次操作是第一次else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 如果这个位置没有元素的话,则通过 CAS 的方式插入数据if (casTabAt(tab, i, null, // 创建一个 Node 添加到数组中,null 表示的是下一个节点为空new Node<K,V>(hash, key, value, null)))// 插入成功,退出循环	break;         }// 如果检测到某个节点的 hash 值是 MOVED,则表示正在进行数组扩容     else if ((fh = f.hash) == MOVED)    // 帮助扩容tab = helpTransfer(tab, f);// 此时,说明已经有线程对Node[]进行了插入操作,后面的插入很有可能会发生Hash冲突else {V oldVal = null;// ----------------synchronized----------------synchronized (f) {// 二次确认此Node对象还是原来的那一个if (tabAt(tab, i) == f) {// ----------------table[i]是链表结点----------------if (fh >= 0) {// 记录结点数,超过阈值后,需要转为红黑树,提高查找效率binCount = 1;            // 遍历这个链表for (Node<K,V> e = f;; ++binCount) {K ek;// 要存的元素的 hash 值和 key 跟要存储的位置的节点的相同的时候,替换掉该节点的 value 即可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;// 如果不是同样的 hash,同样的 key 的时候,则判断该节点的下一个节点是否为空if ((e = e.next) == null) { // ----------------“尾插法”插入新结点----------------pred.next = new Node<K,V>(hash, key,value, null);break;}}}// ----------------table[i]是红黑树结点----------------else if (f instanceof TreeBin) { Node<K,V> p;binCount = 2;// 调用putTreeVal方法,将该元素添加到树中去if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}if (binCount != 0) {// 当在同一个节点的数目达到8个的时候,则扩张数组或将给节点的数据转为treeif (binCount >= TREEIFY_THRESHOLD)// 链表 -> 红黑树 转换treeifyBin(tab, i);    // 表明本次put操作只是替换了旧值,不用更改计数值	if (oldVal != null)return oldVal;break;}}}addCount(1L, binCount);// 计数值加1return null;
}

总结:

put() 方法的核心思想:由于其减小了锁的粒度,若 Hash 完美不冲突的情况下,可同时支持 n 个线程同时 put 操作,n 为 Node 数组大小,在默认大小 16 下,可以支持最大同时 16 个线程无竞争同时操作且线程安全

当 Hash 冲突严重时,Node 链表越来越长,将导致严重的锁竞争,此时会进行扩容,将 Node 进行再散列,下面会介绍扩容的线程安全性。总结一下用到的并发技巧

  • 减小锁粒度:将 Node 链表的头节点作为锁,若在默认大小 16 情况下,将有 16 把锁,大大减小了锁竞争(上下文切换),就像开头所说,将串行的部分最大化缩小,在理想情况下线程的 put 操作都为并行操作。同时直接锁住头节点,保证了线程安全
  • 使用了 volatile 修饰 table 变量,并使用 Unsafe 的 getObjectVolatile() 方法拿到最新的 Node
  • CAS 操作:如果上述拿到的最新的 Node 为 null,则说明还没有任何线程在此 Node 位置进行插入操作,说明本次操作是第一次
  • synchronized 同步锁:如果此时拿到的最新的 Node 不为 null,则说明已经有线程在此 Node 位置进行了插入操作,此时就产生了 hash 冲突;此时的 synchronized 同步锁就起到了关键作用,防止在多线程的情况下发生数据覆盖(线程不安全),接着在 synchronized 同步锁的管理下按照相应的规则执行操作

参考:

【精选】ConcurrentHashMap是如何实现线程安全的_concurrenthashmap如何保证线程安全-CSDN博客

相关文章:

ConcurrentHashMap是如何实现线程安全的

目录 原理&#xff1a; 初始化数据结构时的线程安全 put 操作时的线程安全 原理&#xff1a; 多段锁cassynchronize 初始化数据结构时的线程安全 在 JDK 1.8 中&#xff0c;初始化 ConcurrentHashMap 的时候这个 Node[] 数组是还未初始化的&#xff0c;会等到第一次 put() 方…...

MYSQL:索引与锁表范围简述

一、聚簇索引原则 当有主键索引时&#xff0c;选择主键索引&#xff1b;如果没有主键索引&#xff0c;选择第一个的unique索引&#xff1b;如果都没有就选择隐藏生成的ROW_ID。 二、加锁原则 来自知乎MySQL探秘(七):InnoDB行锁算法 - 知乎 (zhihu.com) 在不通过索引条件查询时…...

15 款 PDF 编辑器帮助轻松编辑、合并PDF文档

PDF 编辑器在当今的数字环境中至关重要&#xff0c;因为 PDF 已成为共享和存储信息的首选格式。只需几分钟&#xff0c;可靠的 PDF 编辑器即可让用户能够根据其特定需求修改、定制和定制文档。在本文中&#xff0c;我们全面汇编了 15 款最佳免费 PDF 编辑器&#xff0c;让您可以…...

PS Raw中文增效工具Camera Raw 16

Camera Raw 16 for mac&#xff08;PS Raw增效工具&#xff09;的功能特色包括强大的图像调整工具。例如&#xff0c;它提供白平衡、曝光、对比度、饱和度等调整选项&#xff0c;帮助用户优化图像的色彩和细节。此外&#xff0c;Camera Raw 16的界面简洁易用&#xff0c;用户可…...

Flink源码解析二之执行计划⽣成

JobManager Leader 选举 首先flink会依据配置获取RecoveryMode,RecoveryMode一共两两种:STANDALONE和ZOOKEEPER。 如果用户配置的是STANDALONE,会直接去配置中获取JobManager的地址如果用户配置的是ZOOKEEPER,flink会首先尝试连接zookeeper,利用zookeeper的leadder选举服务发现…...

MySQL遍历所有表所有字段查找字符数据

MySQL遍历所有表所有字段查找字符数据 工作中有一些数据查找&#xff0c;但是在那个库那个表那个字段中并不明确&#xff0c;特别是敏感字符查找&#xff0c;如果数据量并不大&#xff0c;我们可以采用遍历整个库、表中字符来查找相关数据来解决该问题。 我们可以写一个存储过…...

Java中的异常处理机制是怎样的?

Java中的异常处理机制主要包括以下几个部分&#xff1a; 异常类&#xff08;Exception Class&#xff09;&#xff1a;Java中的异常类继承自java.lang.Throwable&#xff0c;主要分为两大类&#xff1a;Error和Exception。Error表示程序无法处理的严重问题&#xff0c;如系统崩…...

高教社杯数模竞赛特辑论文篇-2023年A题:定日镜场的优化设计(附获奖论文及MATLAB代码实现)

目录 摘 要 一、 问题重述 1.1 问题背景 1.2 问题重述 二、 模型假设 三、 符号说明...

c语言实现http下载功能,显示进度条和下载速率

#include <stdio.h>//printf #include <string.h>//字符串处理 #include <sys/socket.h>//套接字 #include <arpa/inet.h>//ip地址处理 #include <fcntl.h>//open系统调用 #include <unistd.h>//write系统调用 #include <netdb.h>//…...

Educational Codeforces Round 157 (Rated for Div. 2) D. XOR Construction (思维题)

题目 给定长为n-1(n<2e5)的整数序列a&#xff0c;第i个数a[i](0<a[i]<2n) 构造一个长为n的整数序列b&#xff0c;满足&#xff1a; 1. 0到n-1在b数组中每个数恰好出现一次 2. 对于&#xff0c; 题目保证一定有解&#xff0c;有多组时可以输出任意一组 思路来源 …...

【unity实战】实现类似英雄联盟的buff系统

文章目录 先来看看最终效果前言开始BUFF系统加几个BUFF测试1. 逐层消失&#xff0c;升级不重置剩余时间的BUFF2. 一次性全部消失&#xff0c;升级重置剩余时间的BUFF3. 永久BUFF&#xff0c;类似被动BUFF4. 负面BUFF&#xff0c;根据当前BUFF等级计算每秒收到伤害值&#xff0c…...

【C语言基础教程】函数指针与指针大小

文章目录 前言一、函数指针1.1 函数指针的概念1.2 三个示例代码示例1: 使用函数指针调用不同的函数示例 2: 使用函数指针实现回调函数示例 3: 使用函数指针数组 二、指针的大小2.1 前述2.2 指针大小如何决定&#xff1f;两方面理解 总结 前言 在C语言中&#xff0c;指针是一项…...

Web前端—网页制作(以“学成在线”为例)

版本说明 当前版本号[20231105]。 版本修改说明20231105初版 目录 文章目录 版本说明目录day07-学成在线01-项目目录02-版心居中03-布局思路04-header区域-整体布局HTML结构CSS样式 05-header区域-logo06-header区域-导航HTML结构CSS样式 07-header区域-搜索布局HTML结构CSS…...

Hive【Hive(八)自定义函数】

自定义函数用的最多的是单行函数&#xff0c;所以这里只介绍自定义单行函数。 Coding 导入依赖 <dependency><groupId>org.apache.hive</groupId><artifactId>hive-exec</artifactId><version>3.1.3</version></dependency>…...

linux远程桌面管理工具xrdp

一、概述 我们知道&#xff0c;我们日常通过vnc来远程管理linux图形界面&#xff0c;今天分享一工具Xrdp&#xff0c;它是一个开源工具&#xff0c;允许用户通过Windows RDP访问Linux远程桌面。 除了Windows RDP之外&#xff0c;xrdp工具还接受来自其他RDP客户端的连接&#xf…...

100天精通Python(可视化篇)——第106天:Pyecharts绘制多种炫酷桑基图参数说明+代码实战

文章目录 专栏导读一、桑基图介绍1. 桑基图是什么?2. 桑基图应用场景?二、桑基图配置选项1. 导包2. add函数3. 分层设置三、桑基图基础1. 普通桑基图2. 修改标签位置3. 修改节点布局方向4、月度开支桑基图书籍推荐专栏导读 🔥🔥本文已收录于《100天精通Python从入门到就…...

什么是OTP认证?OTP认证服务器有哪些应用场景?

OTP是一次性密码&#xff0c;即只能使用一次的密码。它基于专门的算法&#xff0c;每隔60秒生成一个不可预测的随机数字组合。这种密码的有效期仅在一次会话或交易过程中&#xff0c;因此不容易受到重放攻击。在计算器系统或其他数字设备上&#xff0c;OTP是一种只能使用一次的…...

shell_73.Linux使用新 shell 启动脚本

每次启动新 shell&#xff0c;bash shell 都会运行.bashrc 文件。①对此进行验证&#xff0c;可以使用这种方法&#xff1a;在 主目录下的.bashrc 文件中加入一条简单的 echo 语句&#xff0c;然后启动一个新 shell。 $ cat $HOME/.bashrc # .bashrc # Source global definiti…...

【领域驱动设计】聚合

从战术设计上&#xff0c;DDD 最值得借鉴的就是聚合根 什么是聚合 将实体和值对象在一致性边界之内组合聚合 这里的一致性包括 1、业务概念的完整性 2、业务规则的一致性&#xff1a;多个实体需要在一次操作中保持某种一致性&#xff08;修改 A&#xff0c;同步必须修改 B&a…...

WiFi模块在智能家居中的应用与优化

智能家居技术的迅速发展已经改变了我们对家庭的定义。WiFi模块作为智能设备连接的核心&#xff0c;扮演着连接和控制智能家居生态系统的关键角色。本文将深入研究WiFi模块在智能家居中的应用&#xff0c;同时探讨如何通过优化来提升其性能和用户体验。 1. 智能家居中WiFi模块的…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包

文章目录 现象&#xff1a;mysql已经安装&#xff0c;但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时&#xff0c;可能是因为以下几个原因&#xff1a;1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

Caliper 配置文件解析:fisco-bcos.json

config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...