首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >读写锁 + HashMap 超级组合,真心推荐!

读写锁 + HashMap 超级组合,真心推荐!

作者头像
勇哥java实战
发布2025-02-18 22:07:09
发布2025-02-18 22:07:09
1140
举报
文章被收录于专栏:勇哥编程游记勇哥编程游记

这篇文章,我们深入聊聊:读写锁如何保证 HashMap 成为一个线程安全的容器

1 编程范式例子

上图展示了使用读写锁对 HashMap 进行操作的编程范式,核心要点:

  • 单独的类用于封装对 HashMap 的读、写操作;
  • 读操作方法内部,先获取读锁,读取数据之后,释放读锁;
  • 写操作方法内部,先获取写锁,写入成功之后,释放写锁。

很多同学问我:”勇哥,假如读锁申请成功后,写锁会阻塞吗 ?“ 或者 ”写锁申请成功后,读锁会被阻塞吗?“ 。

答案是肯定的,读写必然互斥 。

笔者分别写两个简单的例子,并展示堆栈图,大家就可以一目了然。

3 读锁申请成功后,写锁会被阻塞

我们将 ReadWriteLockCache 的读操作修改如下:

然后编写 main 方法:

main 方法中,我们先后启动读线程、写线程 。

我们通过 IDEA 打印堆栈日志,发现:读线程先获取读锁,然后休眠 10 秒,这样读锁就不会释放,后面写线程尝试获取写锁时,写线程阻塞了。

3 写锁申请成功后,读锁会被阻塞

我们将 ReadWriteLockCache 的读操作代码还原,然后将写操作修改如下:

然后编写 Main 方法:

main 方法中,我们先后启动写线程、读线程 。

我们通过 IDEA 打印堆栈日志,发现:写线程先获取写锁,然后休眠 10 秒,这样写锁就不会释放,后面读线程尝试获取读锁时,线程阻塞了。

4 使用 ConcurrentHashMap 是不是更简单点

有的同学会问:使用 ConcurrentHashMap 是不是更简单点吗 ?

我们分两个层面来说明:

01 读写锁 + 多个 HashMap

读写锁可以操作多个 HashMap ,每次写操作需要同时变更多个 HashMap ,为了保证其一致性,故需要加锁,ConcurrentHashMap 并发容器在多线程环境下的线程安全也只是针对其自身,故从这个维度,选用读写锁是必然的选择 。

我们举 RocketMQ NameServer 的经典案例:

Broker 启动之后会向所有 NameServer 定期(每 30s)发送心跳包(路由信息),NameServer 会定期扫描 Broker 存活列表,如果超过 120s 没有心跳则移除此 Broker 相关信息,代表下线。

那么 NameServer 如何保存路由信息呢?

路由信息通过几个 HashMap 来保存,当 Broker 向 Nameserver 发送心跳包(路由信息),Nameserver 需要对 HashMap 进行数据更新,但我们都知道 HashMap 并不是线程安全的,高并发场景下,容易出现 CPU 100% 问题,所以更新 HashMap 时需要加锁,RocketMQ 使用了 JDK 的读写锁 ReentrantReadWriteLock 。

  1. 更新路由信息,操作写锁
  1. 查询主题信息,操作读锁

02 读写锁 + 1 个 HashMap

假如我们仅仅使用读写锁操作 1 个 HashMap ,那么我们需要分析下 ConcurrentHashMap 的原理。

1、 JDK 8 之前

从图中我们可以看出, ConcurrentHashMap 内部进行了 Segment 分段,Segment 继承了 ReentrantLock,可以理解为一把锁,各个 Segment 之间都是相互独立上锁的,互不影响。

同一个 Segment 的读写都需要加锁,即落在同一个 Segment 中的读、写操作是串行的,其读的并发性低于读写锁 + HashMap 的,

因此在 JDK 1.8 之前,ConcurrentHashMap 是落后于读写锁 + HashMap 的结构的

2、 JDK 1.8 及其后续版本

JDK 1.8 对 ConcurrentHashMap 代码进行了大幅优化,存储结构与 HashMap 非常类似,同时引入了 CAS 机制(轻量级) 来解决并发更新。

因此,相比读写锁操作 1 个 HashMap, 使用 ConcurrentHashMap 更具性能优势。

5 总结

这篇文章,我们深入剖析:读写锁如何保证 HashMap 成为一个线程安全的容器

1、读写锁编程范式

  • 单独的类用于封装对 HashMap 的读、写操作;
  • 读操作方法内部,先获取读锁,读取数据之后,释放读锁;
  • 写操作方法内部,先获取写锁,写入成功之后,释放写锁。

2、两个实验例子

  • 读锁申请成功后,写线程申请写锁会阻塞
  • 写锁申请成功后,读线程申请读锁会阻塞

我们用两个实验突出了读写锁的特性:读读不互斥,读写互斥,写写互斥

3、使用 ConcurrentHashMap 是不是更简单点

  • 假如需要操作 多个 HashMap ,那么读写锁更加有优势 ;
  • 假如仅仅操作 1个 HashMap , 建议使用 JDK 1.8 ConcurrentHashMap ,性能会更好。
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-02-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 勇哥java实战分享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 编程范式例子
  • 3 读锁申请成功后,写锁会被阻塞
  • 3 写锁申请成功后,读锁会被阻塞
  • 4 使用 ConcurrentHashMap 是不是更简单点
    • 01 读写锁 + 多个 HashMap
    • 02 读写锁 + 1 个 HashMap
  • 5 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档