探索线程安全:HashMap 的四种使用技巧

 这篇文章,我们聊聊线程安全使用 HashMap 的四种技巧。

1 方法内部:每个线程使用单独的 HashMap

如下图,tomcat 接收到到请求后,依次调用控制器 Controller、服务层 Service 、数据库访问层的相关方法。

每次访问服务层方法 serviceMethod 时,都会在方法体内部创建一个单独的 HashMap , 将相关请求参数拷贝到 HashMap 里,然后调用 DAO 方法进行数据库操作。

每个 HTTP 处理线程在服务层方法体内部都有自己的 HashMap 实例,在多线程环境下,不需要对 HashMap 进行任何同步操作。

这也是我们使用最普遍也最安全的的方式,是 CRUD 最基本的操作。

2 配置数据:初始化写,后续只提供读

系统启动之后,我们可以将配置数据加载到本地缓存 HashMap 里 ,这些配置信息初始化之后,就不需要写入了,后续只提供读操作。

上图中显示一个非常简单的配置类 SimpleConfig ,内部有一个 HashMap 对象 configMap 。构造函数调用初始化方法,初始化方法内部的逻辑是:将配置数据存储到 HashMap 中。

SimpleConfig 类对外暴露了 getConfig 方法 ,当 main 线程初始化 SimpleConfig 对象之后,当其他线程调用 getConfig 方法时,因为只有读,没有写操作,所以是线程安全的。

3 读写锁:写时阻塞,并行读,读多写少场景

读写锁是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,而写锁则是互斥锁。

它的规则是:<strong style="font-size: inherit;line-height: inherit;color: rgb (255, 104, 39);"> 读读不互斥,读写互斥,写写互斥 </strong>,适用于读多写少的业务场景。

我们一般都使用 ReentrantReadWriteLock ,该类实现了 ReadWriteLock 。ReadWriteLock 接口也很简单,其内部主要提供了两个方法,分别返回读锁和写锁 。

 public interface ReadWriteLock {//获取读锁Lock readLock();//获取写锁Lock writeLock();
}

读写锁的使用方式如下所示:

  1. 创建 ReentrantReadWriteLock 对象,当使用 ReadWriteLock 的时候,并不是直接使用,而是获得其内部的读锁和写锁,然后分别调用 lock /unlock 方法;
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  1. 读取共享数据 ;
Lock readLock = readWriteLock.readLock();
readLock.lock();
try {// TODO 查询共享数据
} finally {readLock.unlock();
}
  1. 写入共享数据;
Lock writeLock = readWriteLock.writeLock();
writeLock.lock();
try {// TODO 修改共享数据
} finally {writeLock.unlock();
}

下面的代码展示如何使用 ReadWriteLock 线程安全的使用 HashMap :

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockCache {// 创建一个 HashMap 来存储缓存的数据private Map<String, String> map = new HashMap<>();// 创建读写锁对象private ReadWriteLock rw = new ReentrantReadWriteLock();// 放对象方法:向缓存中添加一个键值对public void put(String key, String value) {// 获取写锁,以确保当前操作是独占的rw.writeLock().lock();try {// 执行写操作,将键值对放入 mapmap.put(key, value);} finally {// 释放写锁rw.writeLock().unlock();}}// 取对象方法:从缓存中获取一个值public String get(String key) {// 获取读锁,允许并发读操作rw.readLock().lock();try {// 执行读操作,从 map 中获取值return map.get(key);} finally {// 释放读锁rw.readLock().unlock();}}
}

使用读写锁操作 HashMap 是一个非常经典的技巧,消息中间件 RockeMQ NameServer (名字服务)保存和查询路由信息都是通过这种技巧实现的。

另外,读写锁可以操作多个 HashMap ,相比 ConcurrentHashMap 而言,ReadWriteLock 可以控制缓存对象的颗粒度,具备更大的灵活性。

4 Collections.synchronizedMap : 读写均加锁

如下代码,当我们多线程使用 userMap 时,

static Map<Long, User> userMap = Collections.synchronizedMap(new HashMap<Long, User>());

进入 synchronizedMap 方法:

public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {return new SynchronizedMap<>(m);
}

SynchronizedMap 内部包含一个对象锁 Object mutex ,它本质上是一个包装类,将 HashMap 的读写操作重新实现了一次,我们看到每次读写时,都会用 synchronized 关键字来保证操作的线程安全。

虽然 Collections.synchronizedMap 这种技巧使用起来非常简单,但是我们需要理解它的每次读写都会加锁,性能并不会特别好。

5 总结

这篇文章,笔者总结了四种线程安全的使用 HashMap 的技巧。

1、方法内部:每个线程使用单独的 HashMap

这是我们使用最普遍,也是非常可靠的方式。每个线程在方法体内部创建 HashMap 实例,在多线程环境下,不需要对 HashMap 进行任何同步操作。

2、 配置数据:初始化写,后续只提供读

中间件在启动时,会读取配置文件,将配置数据写入到 HashMap 中,主线程写完之后,以后不会再有写入操作,其他的线程可以读取,不会产生线程安全问题。

3、读写锁:写时阻塞,并行读,读多写少场景

读写锁是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,而写锁则是互斥锁。

它的规则是:<strong style="font-size: inherit;line-height: inherit;color: rgb (255, 104, 39);"> 读读不互斥,读写互斥,写写互斥 </strong>,适用于读多写少的业务场景。

使用读写锁操作 HashMap 是一个非常经典的技巧,消息中间件 RockeMQ NameServer (名字服务)保存和查询路由信息都是通过这种技巧实现的。

4、Collections.synchronizedMap : 读写均加锁

Collections.synchronizedMap 方法使用了装饰器模式为线程不安全的 HashMap 提供了一个线程安全的装饰器类 SynchronizedMap。

通过 SynchronizedMap 来间接的保证对 HashMap 的操作是线程安全,而 SynchronizedMap 底层也是通过 synchronized 关键字来保证操作的线程安全。

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

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

相关文章

vue H5页面video 视频流自动播放, 解决ios不能自动播放问题

视频组件 <videostyle"width: 100%; height: 100%;object-fit: fill"class"player"refplayer_big_boxcontrolspreloadautoplay //自动播放muted //是否静音playsinline"true"x5-playsinline""webkit-playsinline"tru…

[Linux安全运维] Linux用户以及权限管理

Linux用户以及权限管理 Linux用户和组 用户信息文件pasawd /etc/passwd文件用于存储用户的信息 :用于分割不同的字段信息 字段示例&#xff08;第一行&#xff09;含义说明1root用户名2x密码占位符x代表用户有密码存储在shadow文件中无内容代表用户登录系统不需要密码30UID…

前端三件套开发模版——产品介绍页面

今天有空&#xff0c;使用前端三件套html、css、js制作了一个非常简单的产品制作页面&#xff0c;与大家分享&#xff0c;希望可以满足大家应急的需求。本页面可以对产品进行“抢购”、对产品进行介绍&#xff0c;同时可以安排一张产品的高清大图&#xff0c;我也加入了页面的背…

JAVA实现二分查找,斐波那契数列,深度优先搜索详情教程【包含代码】

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

Ubuntu查看opencv版本c++

✗命令行中直接输入&#xff1a; pkg-config --modversion opencv✔命令行中直接输入&#xff1a; pkg-config --modversion opencv4注解&#xff1a;附上在markdown中打勾&#xff0c;对号和打叉。使用时将&和#之间的空格去掉&#xff0c;这里只是为了不让CSDN自动转换才…

vue3单个页面进行防抖节流

防抖 <template><button id"submitButton" ref"submitButton">GET</button> </template><script lang"ts" setup> import { ref, onMounted } from vue;// 防抖函数 function debounce(func: () > void, dela…

小白尝试某程机票信息爬取

实训课需要机票数据集&#xff0c;网上没有&#xff0c;所以我选择爬取数据 此过程可谓经历的九九八十一难&#xff0c;也参考了不少大佬的文章&#xff0c;在此特别记录一下 弯路不多说&#xff0c;我直接讲成功的方法 找到请求url 通过控制台&#xff0c;最后确认下面的 …

基于Java中的SSM框架实现物流管理系统项目【项目源码+论文说明】

基于Java中的SSM框架实现物流管理系统演示 摘要 企业的发展离不开物流的运输&#xff0c;在一个大型的企业中&#xff0c;商品的生产和建设&#xff0c;推广只是前期的一些工作&#xff0c;在后期的商品销售和物流方面的建立&#xff0c;才能让一个企业得到大力的发展。 企业…

基于Linux/ARM/单片机利用状态机对多个按键进行扫描实现短按或者长按

1&#xff09;Linux/ARM/单片机入门级按键扫描程序设计&#xff0c;分享给将要学习或者正在学习Linux/ARM/单片机开发的同学。 2&#xff09;内容属于原创&#xff0c;若转载&#xff0c;请说明出处。 3&#xff09;提供相关问题有偿答疑和支持。 学习Linux/ARM/单片机的同学…

docker部署简单的Kafka

文章目录 1. 拉取镜像2. 运行创建网络运行 ZooKeeper 容器运行 Kafka 容器 3. 简单的校验1. 检查容器状态2. 检查 ZooKeeper 日志3. 检查 Kafka 日志4. 使用 Kafka 命令行工具检查5. 创建和删除测试主题 1. 拉取镜像 选择一组兼容性好的版本。 docker pull bitnami/kafka:3.6…

Facebook:数字社交的引领者与创新者

自2004年诞生以来&#xff0c;Facebook从一个校园网络项目迅速成长为全球最大的社交媒体平台&#xff0c;彻底改变了我们与世界互动的方式。作为数字社交的引领者和创新者&#xff0c;Facebook不仅在技术层面上不断突破&#xff0c;也在社会和文化领域留下了深刻的印记。本文将…

【论文解读】CVPR2024:DUSt3R: Geometric 3D Vision Made Easy

论文“”https://openaccess.thecvf.com/content/CVPR2024/papers/Wang_DUSt3R_Geometric_3D_Vision_Made_Easy_CVPR_2024_paper.pdf 代码&#xff1a;GitHub - naver/dust3r: DUSt3R: Geometric 3D Vision Made Easy DUSt3R是一种旨在简化几何3D视觉任务的新框架。作者着重于…

Docker的架构原理

例子可以想象成一个买手机的场景 clien可以想象 你个人 docker deamon &#xff1a;店员 images&#xff1a; 样机 regisitry&#xff1a; 手机仓库 container: 使用的手机 首先我要在店员买一个手机&#xff0c;店员发现是样机&#xff0c;但是仓库有&#xff0c;&…

11 docker安装redis

目录 安装redis 1. 配置redis配置文件redis.conf 1.1. 找到redis.conf文件 1.2. 配置文件 2. 启动容器 3. 测试redis-cli连接 4. 证明docker使用的是指定的配置文件 安装redis 1. 配置redis配置文件redis.conf 1.1. 找到redis.conf文件 宿主机创建目录/app/redis在/a…

双非怎么进大厂?

https://www.nowcoder.com/share/jump/2764630231719583704126 大家好&#xff0c;我是白露啊。 今天我们要分享一个非常励志的故事&#xff0c;它证明了双非背景的毕业生也可以通过努力和坚持&#xff0c;进入梦想中的大厂。 下面是这位网友的真实经历&#xff0c;希望能为正…

泰安再见,泰山OFFICE还会再见

路过泰安&#xff0c;遇见彩虹。怀念和感恩在泰山信息科技的万丈豪情。 泰山OFFICE&#xff0c;还是要复活。

提升Android Studio开发体验:使用Kelp插件实现颜色和图标预览

提升Android Studio开发体验&#xff1a;使用Kelp插件实现颜色和图标预览 在Android开发中&#xff0c;自动补全功能对于提高开发效率至关重要。然而&#xff0c;默认的Android Studio并不能预览颜色和图标&#xff0c;这使得开发者在选择资源时常常感到困惑。本文将介绍如何使…

LNMP架构搭建Discuz论坛

LNMP架构是一种用于搭建Web服务器环境的常用架构&#xff0c;由Linux、Nginx、MySQL和PHP组成 组成功能Linux作为操作系统的基础&#xff0c;提供稳定的环境Nginx作为反向代理服务器&#xff0c;处理客户端的请求并将他们转发给后端的应用服务器MySQL作为关系型数据库管理系统…

7.2 数据结构

作业 #include <stdio.h> #include <string.h> #include <stdlib.h> struct student {char name[32];int age;double score; }s[3];void stu_input(struct student *s,int n) {printf("请输入%d个学生的信息&#xff08;姓名&#xff0c;年龄&#xff0…

【服装识别系统】图像识别+Python+人工智能+深度学习+算法模型+TensorFlow

一、介绍 服装识别系统&#xff0c;本系统作为图像识别方面的一个典型应用&#xff0c;使用Python作为主要编程语言&#xff0c;并通过TensorFlow搭建ResNet50卷积神经算法网络模型&#xff0c;通过对18种不同的服装&#xff08;‘黑色连衣裙’, ‘黑色衬衫’, ‘黑色鞋子’, …