学习ConcurrentHashMap并发写机制
1. 前言
上篇文章讲了 Unsafe 类中 CAS 的实现,其实是在为这篇文章打基础。不太熟悉的小伙伴请移步Unsafe 中 CAS 的实现。本篇文章主要基于 OpenJDK8 来做源码解析。
2. 源码
ConcurrentHashMap 基于 HashMap 实现。
JDK1.7 和 JDK1.8 作为并发容器在实现上是有差别的。JDK1.7 通过 Segment 分段锁实现,而 JDK1.8 通过 CAS+synchronized 实现。
2.1 ConcurrentHashMap 几个重要方法
在 ConcurrentHashMap 中使用了 unSafe 方法,通过直接操作内存的方式来保证并发处理的安全性,使用的是硬件的安全机制。
private static final sun.misc.Unsafe U;
private static final long SIZECTL;
private static final long TRANSFERINDEX;
private static final long BASECOUNT;
private static final long CELLSBUSY;
private static final long CELLVALUE;
private static final long ABASE;
private static final int ASHIFT;
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentHashMap.class;
SIZECTL = U.objectFieldOffset
(k.getDeclaredField("sizeCtl"));
TRANSFERINDEX = U.objectFieldOffset
(k.getDeclaredField("transferIndex"));
BASECOUNT = U.objectFieldOffset
(k.getDeclaredField("baseCount"));
CELLSBUSY = U.objectFieldOffset
(k.getDeclaredField("cellsBusy"));
Class<?> ck = CounterCell.class;
CELLVALUE = U.objectFieldOffset
(ck.getDeclaredField("value"));
Class<?> ak = Node[].class;
ABASE = U.arrayBaseOffset(ak);
int scale = U.arrayIndexScale(ak);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
}
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
// CAS 将Node插入bucket
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}
2.2 put()流程
还是老规矩,先上流程图帮助阅读源码。

主体源码如下:
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
// 基础数组
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
// 初始化
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 如果bucket==null,即没有hash冲突,CAS插入
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 如果在进行扩容操作,则先扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 否则,存在hash冲突
else {
V oldVal = null;
// 加锁同步
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 遍历过程中出现相同key直接覆盖
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
// 尾插法插入
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 如果是树节点,遍历红黑树
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
put 操作过程如下:
- 如果没有初始化就先调用 initTable()方法来进行初始化过程
- 如果没有 hash 冲突就直接 CAS 插入
- 如果还在进行扩容操作就先进行扩容
- 如果存在 hash 冲突,就加锁来保证线程安全,这里有两种情况,一种是链表形式就直接遍历到尾端插入,一种是红黑树就按照红黑树结构插入
- 最后一个如果该链表的数量大于阈值 8,就要先转换成黑红树的结构,break 再一次进入循环
- 如果添加成功就调用 addCount()方法统计 size,并且检查是否需要扩容
2.3 ConcurrentHashMap 的存储结构
下边的示意图来自网络

3. 结语
本文只分析了 ConcurrentHashMap 的 put() 方法,并没有分析 get()、扩容、删除节点等方法。主要目的是初步了解 ConcurrentMap 确保并发写的设计思路。至此,本篇文章结束,感谢大家的阅读!欢迎大家关注公众号【当我遇上你】。
学习ConcurrentHashMap并发写机制的更多相关文章
- 如何才能够系统地学习Java并发技术?
微信公众号[Java技术江湖]一位阿里Java工程师的技术小站 Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容. 这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些 ...
- 如何深入学习Java并发编程?
在讲解深入学习Java并发编程的方法之前,先分析如下若干错误的观点和学习方法. 错误观点1:学习Java编程主要是学习多线程. 这话其实是说明了表面现象,多线程其实还真是并发编程的实现方式,但在实际高 ...
- 九、Android学习第八天——广播机制与WIFI网络操作(转)
(转自:http://wenku.baidu.com/view/af39b3164431b90d6c85c72f.html) 九.Android学习第八天——广播机制与WIFI网络操作 今天熟悉了An ...
- HBase并行写机制(mvcc)
HBase在保证高性能的同时,为用户提供了便于理解的一致性数据模型MVCC (Multiversion Concurrency Control),即多版本并发控制技术,把数据库的行锁与行的多个版本结合 ...
- MySQL系列:innodb源代码分析之线程并发同步机制
innodb是一个多线程并发的存储引擎,内部的读写都是用多线程来实现的,所以innodb内部实现了一个比較高效的并发同步机制. innodb并没有直接使用系统提供的锁(latch)同步结构,而是对其进 ...
- 1_使用Java文件的并发写
为了实现,并发写操作,首先实验一下在本地情况下, 将一个文件切分成若干个 文件块 然后将文件块 通过多线程的并发的方式写入到指定目录下的文件中. 下面是简单的试着实现代码,暂时 先进行记录一下: im ...
- java学习笔记09--反射机制
java学习笔记09--反射机制 什么是反射: 反射是java语言的一个特性,它允许程序在运行时来进行自我检查并且对内部的成员进行操作.例如它允许一个java的类获取他所有的成员变量和方法并且显示出来 ...
- Linux页快速缓存与回写机制分析
參考 <Linux内核设计与实现> ******************************************* 页快速缓存是linux内核实现的一种主要磁盘缓存,它主要用来降低 ...
- Storm学习笔记 - 消息容错机制
Storm学习笔记 - 消息容错机制 文章来自「随笔」 http://jsynk.cn/blog/articles/153.html 1. Storm消息容错机制概念 一个提供了可靠的处理机制的spo ...
随机推荐
- 蓝桥杯-PREV28-地宫取宝
先自己用dp解了一遍,然后看了官方讲解视频是用记忆化搜索做的.感觉那位老师的方法比较容易实现(效率上和我的差不多的):记录一下三种方法. 动态规划 地宫取宝 1.195KB C++ 正确 100 15 ...
- Angular开发者指南(七)依赖注入
依赖注入 依赖注入(DI)是一种软件设计模式,处理组件如何获取其依赖关系. AngularJS注入器子系统负责创建组件,解析它们的依赖关系,并根据请求将它们提供给其他组件. 使用依赖注入 DI遍布An ...
- 最小生成树 HihoCoder-1097、1098、1109(最小生成树算法)
太久没写最小生成树了,快忘光了.这几天回顾了一下 最小生成树一·Prim算法 AC G++ 369ms 17MB #include "cstdio" using namespace ...
- 吴裕雄--天生自然HTML学习笔记:HTML - XHTML
XHTML 是以 XML 格式编写的 HTML. 什么是 XHTML? XHTML 指的是可扩展超文本标记语言 XHTML 与 HTML 4.01 几乎是相同的 XHTML 是更严格更纯净的 HTML ...
- SpringBoot自动配置的原理
Spring Boot的运行是由注解@EnableAutoConfiguration提供的它的关键功能是@Import注解. EnableAutoConfigurationImportS ...
- dubbo分布式框架下web层调用业务层一直报空指针异常的解决办法
java.lang.NullPointerException............... 环境:SSM(通用mapper)+Dubbo 1.检查导包 提示注解@Reference 应该导入 im ...
- Mac下如何使用homebrew
Homebrew简称brew,是Mac OSX上的软件包管理工具,能在Mac中方便的安装软件或者卸载软件. 常用的命令: 搜索软件:brew search 软件名,如brew search wget ...
- Mac 环境docker 安装jenkins
网上很多的教程是讲的是Linux 上的Docker安装Jenkins,但是我用的是Mac,所以参考之前的前辈写的文章,记录一下自己的安装过程.非常感谢参考文章的前辈写的文章. 参考Docker安装Je ...
- 使用document.domain+iframe跨域实例
首先我们假设主页面地址为:http://www.js8.in/mywork/crossdomain/index.html,我们要加载的内容是位于work.2fool.cn域名下的helloworld. ...
- python登陆接口编写
#coding:utf-8 import getpass,sys i=0 j=0 while i<3: username=raw_input('username:') #输入用户名 life_1 ...