上一篇文章简单的写了一下,BoundedConcurrentHashMap,觉得https://www.cnblogs.com/qiaoyutao/p/10903813.html用的并不多;今天着重写一下ConcurrentHashMap几个常用方法的源码理解,

注: 本文章纯属个人理解,对阅读者只作参考。

先来看构造器

  

public ConcurrentHashMap(Map<? extends K, ? extends V> m) {

     this.sizeCtl = DEFAULT_CAPACITY; // 将表的长度设置为默认的DEFAULT_CAPCITY=16;

     putAll(m); // 调用方法将传进来的map构建成一个线程安全的hash表,下面是putAll的源码

  }
public void putAll(Map<? extends K, ? extends V> m) {
tryPresize(m.size()); // 根据传入的map表的大小来尝试调整此map表的sizeCtl的大小,并创建一个m.size长度的Node<k,v>[]数组给成员变量table。
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) // 遍历集合放入上面的Node数组中。 下面是putVal方法
putVal(e.getKey(), e.getValue(), false);
}
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException(); // 不允许key或者value为null
int hash = spread(key.hashCode()); // 对key进行再hash
int binCount = 0; // 用来计算在这个结点总共有多少个元素, 以此来判断是否需要扩容或者转化为红黑树
for (Node<K,V>[] tab = table;;) { // 此处将Node数组table赋值给tab
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0) // 如果tab也就是Node数组为空那么就进行初始化
tab = initTable(); // initTable方法源码在下面
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // tabAt用来取出tab中(n-1)&hash 下标下的Node结点
if (casTabAt(tab, i, null, // 如果i对应的位置若为null 那么就用cas的方式将此方法的第三个参数添加进来。 这里是通过cas操作的没有加锁
new Node<K,V>(hash, key, value, null))) // 这里的参数null代表新创建的Node的下一个元素为null
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED) /// 当有结点的hash值等于MOVED时,说明该Node数组结点下的hash表正在扩容,那么就帮助扩容(多线程扩容,helpTransfer方法就是帮助扩容)
tab = helpTransfer(tab, f);
else {
          // 进入else 说明该key下有结点,那么就加锁进行操作,如果fh也即是该结点的hash如果大于0说明是一个链表,就循环遍历这个链表并比较key,若期间有key相等的就将value替换,若没有就新创建一个Node节点添加到
// 链表结尾;否则就判断该Node节点是不是TreeBin(红黑树)类型的,就调用putTreeVal将值替换或添加进红黑树中, putTreeVal方法见下面。 当一个结点下的数量超过8时,就将此Hash表转换为红黑树
V oldVal = null;
synchronized (f) {// 对该Node结点进行加锁
if (tabAt(tab, i) == f) { // 取出该结点与前面if中取出的结点比较,如果相同那么说明期间没有别的线程进行操作该结点。
if (fh >= 0) { // 该结点的hash大于0时说明是链表
binCount = 1;
for (Node<K,V> e = f;; ++binCount) { // 遍历该结点下的hash表
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) { // 比较key和key的hash是否相同,如果相同那么就替换value
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) { // 如果没有key相同的Node结点,那么就创建一个并添加到hash表的结尾
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) { // 如果该Node数组结点下的Node结点是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) // 当binCount(结点下的hash表的元素数量大于等于TREEIFY_THRESHOLD(8)时,就转换为红黑树)
treeifyBin(tab, i);
if (oldVal != null)
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) { // 判断Node数组是否为空,不为空的话说明hash表已经初始化。
if ((sc = sizeCtl) < 0) // 当前sizeCtl < 0时,说明该数组正在被别的线程操作(或者扩容,或者初始化), 那么就用Thread.yield挂起线程
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
         /** U.compareAndSwapInt(this,SIZECTL,sc,-1) 是java的Unsafe类的方法,使用了CAS算法来保证操作的原子性;何为CAS也即比较并替换,这里只大概的讲解,具体请参考https://www.jianshu.com/p/fb6e91b013cc
         * CAS是根据内存地址来判断值有没有被修改, 于是this:表示被操作的对象,SIZECTL:是当前的sizeCtl的值,sc是sizeCtl的旧值,-1是即将要替换成的值。 compareAndSwapInt先会比较SIZECTL的值与sizeCtl是否相同如果
         * 相同说明在期间没有操作hash表,就把当前sizeCtl的值改为-1,并返回true,告诉其他线程,有线程正在初始化hash表。如果不相同说明有线程操作正在初始化hash表,其他线程就会等待挂起。
try {
if ((tab = table) == null || tab.length == 0) { // 如果Node数组还没有初始化
int n = (sc > 0) ? sc : DEFAULT_CAPACITY; // 确定Node数组的大小。
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; // 创建数组
table = tab = nt; // 将数组给table和tab,给tab的目的是告诉其他线程,hash表已经初始化。
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc; // 将数组长度无符号右移位2的次幂更新到sizeCtl
}
break;
}
}
return tab;
}

其他的put方法也都是调用了pubVal(),  至于get等方法就不用写了,也就是比较key取value。

java 并发容器一之ConcurrentHashMap(基于JDK1.8)的更多相关文章

  1. java 并发容器一之BoundedConcurrentHashMap(基于JDK1.8)

    最近开始学习java并发容器,以补充自己在并发方面的知识,从源码上进行.如有不正确之处,还请各位大神批评指正. 前言: 本人个人理解,看一个类的源码要先从构造器入手,然后再看方法.下面看Bounded ...

  2. Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进(转)

    一.简单回顾ConcurrentHashMap在jdk1.7中的设计 先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示: 每一个segment都是一个HashE ...

  3. Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进

    一.简单回顾ConcurrentHashMap在jdk1.7中的设计 先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示: 每一个segment都是一个HashE ...

  4. Java并发容器——ConcurrentSkipListMap和ConcurrentHashMap

    一:ConcurrentSkipListMap TreeMap使用红黑树按照key的顺序(自然顺序.自定义顺序)来使得键值对有序存储,但是只能在单线程下安全使用:多线程下想要使键值对按照key的顺序来 ...

  5. Java并发编程系列-(5) Java并发容器

    5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...

  6. Java 并发系列之六:java 并发容器(4个)

    1. ConcurrentHashMap 2. ConcurrentLinkedQueue 3. ConcurrentSkipListMap 4. ConcurrentSkipListSet 5. t ...

  7. Java并发指南14:Java并发容器ConcurrentSkipListMap与CopyOnWriteArrayList

    原文出处http://cmsblogs.com/ 『chenssy』 到目前为止,我们在Java世界里看到了两种实现key-value的数据结构:Hash.TreeMap,这两种数据结构各自都有着优缺 ...

  8. 《Java并发编程的艺术》第6/7/8章 Java并发容器与框架/13个原子操作/并发工具类

    第6章 Java并发容器和框架 6.1  ConcurrentHashMap(线程安全的HashMap.锁分段技术) 6.1.1 为什么要使用ConcurrentHashMap 在并发编程中使用Has ...

  9. 【java并发容器】并发容器之CopyOnWriteArrayList

    原文链接: http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略.其基本思路是,从一开始大家都在共享同一个内容 ...

随机推荐

  1. 经验收获Linux终端下方便命令

    一.命令行日常系快捷键 如下的快捷方式非常有用,能够极大的提升你的工作效率: CTRL + U - 剪切光标前的内容 CTRL + K - 剪切光标至行末的内容 CTRL + Y - 粘贴 CTRL ...

  2. [poj2778]DNA Sequence(AC自动机+矩阵快速幂)

    题意:有m种DNA序列是有疾病的,问有多少种长度为n的DNA序列不包含任何一种有疾病的DNA序列.(仅含A,T,C,G四个字符) 解题关键:AC自动机,实际上就是一个状态转移图,注意能少取模就少取模, ...

  3. Ubuntu解压windows下的.zip文件出现乱码的解决办法

    乱码类似这样的:╫╩┴╧╖┤╤▌▓т╒╛╦┘╢╚│ 这个主要是因为zip文件对文件名的编码默认为当前环境的locale,如在windows下压缩的zip文件,在linux下其中的中文名便会乱码.这是z ...

  4. HDOJ-1263

    水果 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submissi ...

  5. error:未定义的引用

    用qtcreator编程的话,先在.pro文件中这样写: INCLUDEPATH += \ /usr/local/include/ LIBS += \ -L"/usr/local/lib&q ...

  6. HDU - 1099 - Lottery - 概率dp

    http://acm.hdu.edu.cn/showproblem.php?pid=1099 最最简单的概率dp,完全是等概率转移. 设dp[i]为已有i张票,还需要抽几次才能集齐的期望. 那么dp[ ...

  7. HDU3038【种类并查集】

    题意: 给出m组区间[a,b],以及其区间的和,问有矛盾的有几组: 思路: 种类并查集. 主要是几个关系:同类元素的关系,父亲与儿子的关系,不同类元素的关系: 我们可以类似看作一个前缀和,sum[x] ...

  8. 如何有效提升Unity Gear VR游戏性能

    http://www.vr186.com/vr_news/vr_technical_area/1093.html 好的,所以你决定用 Unity 来做一个 VR 游戏,并选定了三星 Gear vr 为 ...

  9. c#二维数组传递与拷贝

    定义 string[,] arr = new string[12, 31] 另一种string[][] ary = new string[5][];相当于一维数组 常量二维数组定义, 用readonl ...

  10. bzoj 3876: [Ahoi2014&Jsoi2014]支线剧情【有上下界有源汇最小费用最大流】

    每条边流量有下界有费用,很显然是有上下界有源汇最小费用最大流 连边(s,1,(0,inf),0),(i,t,(0,inf),0),表示从1出发inf次从每个点结束inf次 连边(i,j,(1,inf) ...