线程安全使用 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(); }
读写锁的使用方式如下所示:
- 创建 ReentrantReadWriteLock 对象 , 当使用 ReadWriteLock 的时候,并不是直接使用,而是获得其内部的读锁和写锁,然后分别调用 lock / unlock 方法 ;
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
- 读取共享数据 ;
Lock readLock = readWriteLock.readLock(); readLock.lock(); try { // TODO 查询共享数据 } finally { readLock.unlock(); }
- 写入共享数据;
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 { // 执行写操作,将键值对放入 map map.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 关键字来保证操作的线程安全。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
得物质量管理体系的建设与应用
一、背景 质量保障是一门基于软件测试的系统化工程,遵循渐进式的发展规律。通过因地制宜地制定落地策略,设计场景方案,获取试验结果,并加以循环往复。最终,在每一位得物测试工程师的共同努力下,积累出一套适应得物技术的质量保障方法论,即本文介绍的得物质量管理体系。 二、建设“四化”体系 得物质量管理体系,历经三年的建设,现已拥有了完备的机制、流程、方法以及工具。目标是通过实现标准化、线上化、自动化以及智能化,最终形成立体的得物质量管理体系。其中: “机制”即:(研发)质量保障机制。以“迭代质量评审机制”为基础,每迭代开展,上线风险可控,方可准出。 “流程”即:测试&协同流程。分为测试流程以及协同流程。测试流程的应用,用于不偏不倚地反映研发质量;辅以测试质量校准。协同流程的有效应用则会帮助测试质量提升。因此,引入协同质量,用以稳定测试质量,保障最终线上稳定。 “方法”即:落地实施方法。以业务域“质量月”活动为主要载体,持续优化研发过程质量,包括跨域评审、CR合并、准时提测、冒烟通过、缺陷日清、缺陷引入、缺陷逃逸等基础指标。 “工具”即:质量工程工具。围绕稳定、质量、效率、安全、体验、合规...
- 下一篇
我欺骗了CTO,但拯救了公司(附HN热评)
原文 The One Where I Lie To The CTO 这是几年前的事了。我刚开始我的职业生涯,我爸跟我说,要想做好工作,有时候需要不顾老板的意见去做事。他表达的其实是,可以让你的老板因为你而成功和满意,也可以选择把每一个决策都交给老板决定,但结果往往是大家都不开心也不成功。 当时我在一家财富 500 强公司工作,我们的 CTO 接了个他有私交的重要客户的一重大项目。他还决定把项目中的一个关键部分外包给一家大型技术服务公司,这家公司声称他们的一款产品可以帮我们完成大部分繁重工作。 在我职业生涯中常见的一幕再次上演:供应商所说的「产品」,实际上只是勉强能称之为产品的东西,勉强能满足我们的需要,需要大量定制后才勉强能用。显然,通过对他们的「产品」进行定制,我们巧妙地集供应商软件的缺点与定制软件的所有缺点于一身。我们还无意中创造了最糟糕的主意:一个僵硬的供应商软件,被迫做它本不应做的事,同时还从他们的主产品代码库中分叉出来 - 一旦供应商认识到维护成本过高,这个产品迟早会被淘汰。我们为此互相埋怨,认为这是一个极其糟糕的想法,尤其是考虑到供应商一贯不按时交付的记录。 由于 CTO ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6