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

news/2024/11/16 23:43:26/文章来源:https://www.cnblogs.com/makemylife/p/18200927

这篇文章,我们聊聊线程安全使用 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 读写锁:写时阻塞,并行读,读多写少场景

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

它的规则是:读读不互斥,读写互斥,写写互斥,适用于读多写少的业务场景。

我们一般都使用 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、读写锁:写时阻塞,并行读,读多写少场景

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

它的规则是:读读不互斥,读写互斥,写写互斥,适用于读多写少的业务场景。

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

4、Collections.synchronizedMap : 读写均加锁

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

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


如果我的文章对你有所帮助,还请帮忙点赞、在看、转发一下,你的支持会激励我输出更高质量的文章,非常感谢!

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

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

相关文章

C - AtCoder Magics

C - AtCoder Magics https://atcoder.jp/contests/abc354/tasks/abc354_c思路 首先按照a属性对数列进行排序,大的在前,小的在后, 完成后, 则数列在a参数上是非递增的。 如下图中x轴对应 a 参数, y轴对应c参数, discard条件,实际上是找出 数列 对于c参数 沿着 a 参数非递…

『手撕Vue-CLI』添加自定义指令

前言 经上篇『手撕Vue-CLI』添加帮助和版本号的介绍之后,已经可以在控制台中输入 nue --help 来查看帮助信息了,但是在帮助信息中只有 --version,--help 这两个指令,而 vue-cli 中还有很多指令,例如 create,serve,build 等等,所以本章将继续添加自定义指令,例如 creat…

Web入门

SQL注入 数据库基础 *关系型数据库: #Access#MSSQL -- 1433*非关系型数据库: #MySQL -- 3306#Oracle -- 1521等对MySQL数据库的操作 1.显示数据库 show databases;2.显示数据库版本 select version(); 3.使用数据库 use XXX;4.显示当前正在使用的数据库 selec…

Linux常用命令-文件目录命令

Linux常用命令-文件目录命令1.目录命令 1.1、ls命令:显示目录下的内容 基本格式 [root@localhost ~]# ls [选项] [参数是文件名或目录名]常用选项 -a:显示所有文件。 --color=when。支持颜色输出,when的值默认是always(总显示颜色),never(不显示颜色)和auto(自动)。 -d:显…

asdf

asf本文版权归作者和博客园共有,欢迎转载,转载请注明原文链接:https://www.cnblogs.com/lllliuxiaoxia/p/18200863另外欢迎关注公众号,一起讨论学习

啊手动阀手动阀

阿斯顿发射点发射点发生发射点发撒打发本文来自博客园,作者:胖树,转载请注明原文链接:https://www.cnblogs.com/lllliuxiaoxia/p/18200857关注我的公众号不定期推送资讯

配置SQLServer远程连接

要在 SQL Server 上启用远程连接,需要执行以下步骤:1、确保 SQL Server 已启用远程连接:  登录到 SQL Server 所在的计算机上。  打开 SQL Server Management Studio (SSMS)。  使用 Windows 身份验证或 SQL Server 身份验证登录 SQL Server。  在左侧的对象资源管理…

uCTRL论文阅读笔记

uCTRL: Unbiased Contrastive Representation Learning via Alignment and Uniformity for Collaborative Filtering论文阅读笔记 这篇文章应该是关于无偏推荐的 Abstract ​ 由于协作过滤(CF)模型的隐式用户反馈偏向于流行的项目,CF模型倾向于产生带有流行偏差的推荐列表。…

SQL Server 2012提供了多种备份和还原数据库的方法,包括以下几种:

SQL Server 2012提供了多种备份和还原数据库的方法,包括以下几种:SQL Server Management Studio(SSMS):SSMS是一个支持图形用户界面的工具,可以通过它备份和还原整个数据库或特定的数据表、视图等。在SSMS中,可以通过右键单击数据库并选择“任务” > “备份”或“还原…

BUUCTF-WEB(15-20)

[极客大挑战 2019]BabySQL 打开题目就试了试万能密码,是不行的推测应该做了过滤 我们密码框输入1 1 or 1=1试试发现我们的or没了,union,select,where都被过滤了,应该是被替换成空字符,所以我们可以双写绕过 uunionnion sselectelect wwherehere由于这里or被过滤了,我们无…

11个国内外免费域名解析服务

11个国内外免费域名解析服务 一般域名使用注册商提供的域名解析服务虽然方便,但功能大多有限,特别是目前国内还会针对某些DNS服务器进行屏蔽,造成网站无法解析的情况出现,因此,使用第三方域名解析服务也是中国网站的必要选择,这里就介绍一些常见的免费域名解析服务。 域名…