ConcurrentHashmap算是我看的集合源码里最难理解的了(当然ConcurrentLinkedList虽然代码少但理解起来也累),在Java1.8版本中DougLea大师巧通过妙地代码把锁粒度已经将成桶级别了,不得不说非常厉害。本文暂时贴上代码,内容后续补充。

看ConcurrentHashmap之前要掌握的基础。

1、对Hashmap的原理了解。

2、Volatile关键字、CAS操作和Synchronized关键字要理解。

3、配合网上解析和并发的书一同食用,而且要看源码里的注释,看源码前先了解其运作过程。

推荐一篇源码解析:https://www.jianshu.com/p/487d00afe6ca

推荐的书《Java并发编程的艺术》 集合那章

正文

get操作

    public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode()); //高16位与低6位散列
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0) //eh小于0表示该table正在扩容,将旧table上的node移到新table上,被移过去的节点旧位置上标记一个hash<0的node
                   //find就是用this找到本该在这里的节点,然后判断是否为null返回相应值。
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}

put操作

public V put(K key, V value) {
return putVal(key, value, false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new Nul lPointerException();
int hash = spread(key.hashCode()); //(h ^ (h >>> 16)) & HASH_BITS
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(); //初始化Node数组table
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//数组不为空,分配到的地址中没有node
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))//cas操作把它加放入该地址
break; // no lock when adding to empty bin 数组每个位置上的第一个节点不需要获得锁
}
else if ((fh = f.hash) == MOVED)//?? 好像是扩容时被put的操作
tab = helpTransfer(tab, f);
else {//数组被初始化了且地址不为空,非扩容时期的正常操作
V oldVal = null;
synchronized (f) {//拿到该位置第一个节点的对象锁
if (tabAt(tab, i) == f) {//再次确认头节点
if (fh >= 0) {//头节点hash>0??
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {//找到相同的key,更新值
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) { //f是tree节点,用红黑树方法
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)//如果是更新值,返回oldval
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0) //已被别人抢先初始化了(第一个初始化的线程将sizeCtl改为-1) 进入准备状态(等待被唤醒
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY; //sizeCtl大于0就使用它的大小,等于0默认容量大小
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];//new个node数组
table = tab = nt;
sc = n - (n >>> 2);//sc=0.75n
}
} finally {
sizeCtl = sc;//sizeCtl为0.75n sizeCtl像是数组扩容阈值
}
break;
}
}
return tab;
}
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) { //与后面addcount()函数代码相似,这里就不解释了,直接往后看。
Node<K,V>[] nextTab; int sc;                //函数大意就是 满足一定条件也进入transfer方法 帮助扩容。
if (tab != null && (f instanceof ForwardingNode) && //
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
int rs = resizeStamp(tab.length);
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null || //countercells为2e幂,应该相当于数组长度 basecount应该是实时键值对数量
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) { //键值对数量超过了阈值,且小于最大值
int rs = resizeStamp(n); //Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
if (sc < 0) {//其他线程正在扩容
//第一个条件:因为第一个线程扩容后会将sc设为rs << RESIZE_STAMP_SHIFT) + 2),它退回去会等于rs,如果
//不等于说明第一个线程还没开始扩容。
//第二、三个条件:未知
//第四个条件:新数组还没创建
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break; //不帮助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) //帮助扩容 s=sc+1
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2)) //初次扩容 将值设为很小的负数
transfer(tab, null);
s = sumCount();
}
}
}
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride; //stride步长 切割迁移数组为小份进行转移,用来设置transferIndex
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) //NCPU大于1则为 n/(8*NCPU) 否则为数组长度。但要保证大于16
stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) { // initiating //初始化nextTab,只在扩容时不为null
try {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1]; //数组长度翻倍
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE; //翻倍失败因为它是最大值了
return;
}
nextTable = nextTab; //新数组
transferIndex = n; //转移指针开始为原数组长度
}
int nextn = nextTab.length; //扩容数组长度
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); //已迁移节点 他的hash为-1
boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;//指针指向下一个准备转移节点 界限指向划分该线程任务的终节点
if (--i >= bound || finishing) //i >= bound 说明节点到达了界限,它的任务完成或 finish
advance = false;
else if ((nextIndex = transferIndex) <= 0) { //倒序转移全部转移完成了
i = -1;// 准备退出迁移
advance = false;
}
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) { //将转移指针按步长递减(开始是原数组长度)
bound = nextBound;//界限=nextIndex(开始为原数组长度)-步长
i = nextIndex - 1;//i=nextIndex-1
advance = false; //跳出
}
}
if (i < 0 || i >= n || i + n >= nextn) {//bound为0,i<0 或 i>=数组长度 或i+原长度>=现长度
int sc; //可能原因是原数组长度为0则i<0 ,或,或已经是最大值不能扩容?
if (finishing) { //如果完成了就将nextTable清除,
nextTable = null;
table = nextTab; //将扩容后数组作为当前数组
sizeCtl = (n << 1) - (n >>> 1); //sizeCtl 为1.5 倍
return; //返回
}
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { //将SCTL 折为 sc-1成功(帮助转移时+1)现在减回去
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) //之前正常状态 将SIZECTL 设为了 rs << RESIZE_STAMP_SHIFT) + 2
return; //现在返回去,不等则说明其他线程还没转移完
finishing = advance = true;
i = n; // recheck before commit
}
}
else if ((f = tabAt(tab, i)) == null) //如果原tab上的某地址首节点为null 则换为 fwd
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED) //如果它 hash为 -1(MOVED) 说明已被移动
advance = true; // already processed
else {
synchronized (f) {//首节点上锁
if (tabAt(tab, i) == f) { //再次确认首节点
Node<K,V> ln, hn;
if (fh >= 0) { //首节点hash大于0
int runBit = fh & n; //截取hash确定位置 n是扩容前长度
Node<K,V> lastRun = f;
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
}
else {
hn = lastRun;
ln = null;
}
for (Node<K,V> p = f; p != lastRun; p = p.next) {
int ph = p.hash; K pk = p.key; V pv = p.val;
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
setTabAt(nextTab, i, ln);//将链好的lownode首节点放入新数组低位
setTabAt(nextTab, i + n, hn);//将链好的hinode首节点放入新数组高位
setTabAt(tab, i, fwd);//把旧数组位置上hash设为-1
advance = true;
}
else if (f instanceof TreeBin) { //treebin的方法
TreeBin<K,V> t = (TreeBin<K,V>)f;
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node<K,V> e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K,V>(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K,V>(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0) //已被别人抢先初始化了(第一个初始化的线程将sizeCtl改为-1) 进入准备状态(等待被唤醒
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY; //sizeCtl大于0就使用它的大小,等于0默认容量大小
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];//new个node数组
table = tab = nt;
sc = n - (n >>> 2);//sc=0.75n
}
} finally {
sizeCtl = sc;//sizeCtl为0.75n sizeCtl像是数组扩容阈值
}
break;
}
}
return tab;
}

源码解析之ConcurrentHashmap的更多相关文章

  1. 【JUC源码解析】ConcurrentHashMap

    简介 支持并发的哈希表.其中包括红黑树,扩容,分槽计数等知识点. 源码分析 常量 private static final int MAXIMUM_CAPACITY = 1 << 30; ...

  2. Java之ConcurrentHashMap源码解析

    ConcurrentHashMap源码解析 目录 ConcurrentHashMap源码解析 jdk8之前的实现原理 jdk8的实现原理 变量解释 初始化 初始化table put操作 hash算法 ...

  3. ConcurrentHashMap源码解析(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http:/ ...

  4. 第二章 ConcurrentHashMap源码解析

    注:在看这篇文章之前,如果对HashMap的层不清楚的话,建议先去看看HashMap源码解析. http://www.cnblogs.com/java-zhao/p/5106189.html 1.对于 ...

  5. ConcurrentHashMap源码解析,多线程扩容

    前面一篇已经介绍过了 HashMap 的源码: HashMap源码解析.jdk7和8之后的区别.相关问题分析 HashMap并不是线程安全的,他就一个普通的容器,没有做相关的同步处理,因此线程不安全主 ...

  6. Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析

    目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...

  7. HashMap 源码解析

    HashMap简介: HashMap在日常的开发中应用的非常之广泛,它是基于Hash表,实现了Map接口,以键值对(key-value)形式进行数据存储,HashMap在数据结构上使用的是数组+链表. ...

  8. EventBus源码解析 源码阅读记录

    EventBus源码阅读记录 repo地址: greenrobot/EventBus EventBus的构造 双重加锁的单例. static volatile EventBus defaultInst ...

  9. Dubbo 源码解析四 —— 负载均衡LoadBalance

    欢迎来我的 Star Followers 后期后继续更新Dubbo别的文章 Dubbo 源码分析系列之一环境搭建 Dubbo 入门之二 --- 项目结构解析 Dubbo 源码分析系列之三 -- 架构原 ...

随机推荐

  1. MySQL 设置root密码报错:mysqladmin: connect to server at 'localhost' failed

    MySQL 设置root密码报错:mysqladmin: connect to server at 'localhost' failed 1.安装完MySQL设置root密码报错如下 [root@vm ...

  2. xmind 8 便携版:关联文件后,双击打开文件,在当前文件夹产生configuration子文件的问题解决办法

    Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\.xmind] @="XMind.Workbook.3" " ...

  3. python 函数内使用自己的函数名

    def p(): import sys print sys._getframe(1).f_code.co_name def f(): p() def f1(): p() if __name__ == ...

  4. Xorboot-UEFI新手入门教程

    Xorboot-UEFI新手入门教程        Xorboot-UEFI是一款UEFI下轻量级的图形化多系统引导程序,pauly于2014年国庆节期间发布了预览版.搜了下论坛,关于Xorboot- ...

  5. ES - 处理TooManyClause异常

    1.TooManyClause 我们在使用terms query.prefix query.fuzzy query.wildcard query.range query的时候,一不小心就会遇到TooM ...

  6. Python3学习的准备工作

    简单好用的桌面开发平台:ubuntu 16.x/18.x 或 LinuxMint 18.x 开发工具:新版操作系统都自带有Python3.5及更高版本 其实作为初学者,不要迷信版本,也不必着急升级成最 ...

  7. Arrays和String单元测试

    20175227张雪莹 2018-2019-2 <Java程序设计> Arrays和String单元测试 要求 在IDEA中以TDD的方式对String类和Arrays类进行学习 测试相关 ...

  8. Listen and Write 18th Feb 2019

    Weighted blanket has becomes very popular in many homes. they claim it can provide better sleep and ...

  9. 获取geometry边界范围的示例代码

    根据sqlserver geometry数据定义获取空间类型边界范围 --获取指定街道边界的xy最大最小值 ) '--街道编码 create table #temp_point(id int iden ...

  10. spring入门 依赖入注的三种方式(1)

    第一种:构造器参数注入 第二种:setter方法属性注入(setter方法的规范-JavaBean规范) 第三种:接口注入 Bean 属性的注入:对一个对象的属性的赋值 1.构造器参数注入: publ ...