ConcurrentHashMap 与 CAS
在做接口限流时涉及到了一个有意思问题,牵扯出了关于concurrentHashMap的一些用法,以及CAS的一些概念。限流算法很多,我主要就以最简单的计数器法来做引。先抽象化一下需求:统计每个接口访问的次数。一个接口对应一个url,也就是一个字符串,每调用一次对其进行加一处理。可能出现的问题主要有三个:
- 多线程访问,需要选择合适的并发容器
- 分布式下多个实例统计接口流量需要共享内存
- 流量统计应该尽可能不损耗服务器性能
但这次的博客并不是想描述怎么去实现接口限流,而是主要想描述一下遇到的问题,所以,第二点暂时不考虑,即不使用Redis。
说到并发的字符串统计,立即让人联想到的数据结构便是ConcurrentHashpMap<String,Long> urlCounter;
如果你刚刚接触并发可能会写出如代码清单1的代码
代码清单1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
|
都说concurrentHashMap是个线程安全的并发容器,所以没有显示加同步,实际效果呢并不如所愿。
问题就出在increase方法,concurrentHashMap能保证的是每一个操作(put,get,delete…)本身是线程安全的,但是我们的increase方法,对concurrentHashMap的操作是一个组合,先get再put,所以多个线程的操作出现了覆盖。如果对整个increase方法加锁,那么又违背了我们使用并发容器的初衷,因为锁的开销很大。我们有没有方法改善统计方法呢?
代码清单2罗列了concurrentHashMap父接口concurrentMap的一个非常有用但是又常常被忽略的方法。
代码清单2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
这其实就是一个最典型的CAS操作,except that the action is performed atomically.这句话真是帮了大忙,我们可以保证比较和设置是一个原子操作,当A线程尝试在increase时,旧值被修改的话就回导致replace失效,而我们只需要用一个循环,不断获取最新值,直到成功replace一次,即可完成统计。
改进后的increase方法如下
代码清单3:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
再次调用后获得了正确的结果,上述方案看上去比较繁琐,因为第一次调用时需要进行一次初始化,所以多了一个判断,也用到了另一个CAS操作putIfAbsent,他的源代码描述如下:
代码清单4:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
简单翻译如下:“如果(调用该方法时)key-value 已经存在,则返回那个 value 值。如果调用时 map 里没有找到 key 的 mapping,返回一个 null 值”。值得注意点的一点就是concurrentHashMap的value是不能存在null值的。实际上呢,上述的方案也可以把Long替换成AtomicLong,可以简化实现, ConcurrentHashMap
1 2 3 4 5 6 7 8 9 10 11 |
|
看一下他的源码就会发现,其实和代码清单3思路差不多,只不过功能更完善了一点。
和CAS很像的操作,我之前的博客中提到过数据库的乐观锁,用version字段来进行并发控制,其实也是一种compare and swap的思想。
杂谈:网上很多对ConcurrentHashMap的介绍,众所周知,这是一个用分段锁实现的一个线程安全的map容器,但是真正对他的使用场景有介绍的少之又少。面试中能知道这个容器的人也确实不少,问出去,也就回答一个分段锁就没有下文了,但我觉得吧,有时候一知半解反而会比不知道更可怕。
如有披露或问题欢迎留言或者入群探讨
ConcurrentHashMap 与 CAS相关推荐
- Java 并发实践 — ConcurrentHashMap 与 CAS
转载自 Java 并发实践 - ConcurrentHashMap 与 CAS 最近在做接口限流时涉及到了一个有意思问题,牵扯出了关于concurrentHashMap的一些用法,以及CAS的一些概念 ...
- Java 8 ConcurrentHashMap源码中竟然隐藏着两个BUG
Java 7的ConcurrenHashMap的源码我建议大家都看看,那个版本的源码就是Java多线程编程的教科书.在Java 7的源码中,作者对悲观锁的使用非常谨慎,大多都转换为自旋锁加volati ...
- ConcurrentHashMap 学习笔记
# ConcurrentHashMap HashMap虽然好用,但是它却不是线程安全的,而在并发度较高的现在,在有些情况下它可能就不是那么合适了,所以需要一个线程安全键值对结构. Hashtable是 ...
- 今天轮到我来撕ConcurrentHashMap了
目录 前言 一.ConcurrentHashMap的实现原理 1. JDK1.7:Segment+HashEntry 2.JDK1.8:synchronized+CAS+红黑树 3.JDK1.7与JD ...
- 面试必会系列 - 1.2 Java 集合,源码讲解
本文已收录至 github,完整图文:https://github.com/HanquanHq/MD-Notes 容器 连老师在公开课里面讲过相关的源码 Collection List CopyOnW ...
- java 线程开销_多线程的线程开销
多线程中两个必要的开销:线程的创建.上下文切换 创建线程: 创建线程使用是直接向系统申请资源的,对操作系统来说,创建一个线程的代价是十分昂贵的, 需要给它分配内存.列入调度,同时在线程切换的时候还要执 ...
- HashMap、ConcurretnHashMap面试题详解,源码分析
文章目录 面试题 HashMap.LinkedHashMap和TreeMap的区别是什么? ①:为什么hashmap每次扩容大小为2的n次方? ③:jdk1.7的hashmap的扩容操作是在元素插入之 ...
- 安卓线程同步面试_面试BAT大厂,可少不了这些题目!
点击上方的终端研发部,右上角选择"设为星标" 每日早8点半,技术文章准时送上 公众号后台回复"学习",获取作者独家秘制精品资料 往期文章 走出腾讯和阿里,大厂员 ...
- Java面试题--dalao总结版
1.UUID:当前日期和时间+时钟序列+全局唯一的IEEE机器识别号(如果有网卡,从网卡MAC地址获取) import java.util.UUID; UUID.randUUID().toString ...
最新文章
- UIBezierPath的使用(持续更新)
- Android之ActionBar常用设计和使用总结
- QTP自传之web常用对象
- Phaser开源2d引擎 javascript/html5游戏框架
- apache 添加下载文件头
- CSS两栏布局之右栏布局
- Oracle技术之SYS用户对象不支持延迟段
- 基于ARM的SoC设计入门
- 基于docker的Mongodb部署文档 文件映射方式
- 谷歌软件工程师_这是我曾经在Google担任软件工程师的简历。
- 天线匹配与人体之间的关系
- 安卓修改Airpods的双击功能,改“播放暂停”————下一首
- Image Pro Plus6进行荧光分析与荧光照片合成学习笔记
- python编程语言进化_编程语言的进化史和分类
- et文件怎么转成excel_excel怎么转换成word格式的文件格式?这些方法应该掌握!...
- 如何做一个优秀的团队成员
- http服务器常见状态码
- 大学计算机基础实践教程实验一讨论与思考,大学计算机基础实验报告手册(非艺术类).doc...
- (二)zynq芯片是什么
- 使用ajax访问腾讯地图api,腾讯地图ajax获取路线规划结果