上一篇文章简单的写了一下,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. codeforces 652C C. Foe Pairs(尺取法+线段树查询一个区间覆盖线段)

    题目链接: C. Foe Pairs time limit per test 1 second memory limit per test 256 megabytes input standard i ...

  2. tcpdump 探测器分析

    注:默认情况下,tcpdump临听它遇见的第一个网络接口,如果它选择了错误的接口,可以-i标志强行指定接口,如果DNS不能用,或者只是不希望tcpdump进行名字查找,请使用-n选项,这个选项(-n) ...

  3. vue-router 获得上一级路由以及返回上一级路由的方法

    if (this.$store.state.previousRouter.name) { this.$router.push({name: this.$store.state.previousRout ...

  4. git 系统中 post-receive 钩子不能正常执行 git pull 解决方法

    有一个需求是本地git在push到远程 git repo 之后,在远程服务器上自动在/dir/foo下执行 git pull 的操作.想来是一个很简单的需求,不就是在远程的 foo.git 仓库中的 ...

  5. log4j的学习和log4j在程序中使用的加载作用过程

    昨天进行代码评审的时候,大家都纠结在了日志信息应该如何输出上,其实我想大家应该一直都在使用log4j来对日志信息进行输出,但是未想应该有很大一部分人对log4j是不了解的,我遇到这个问题的时候也到网上 ...

  6. Coloring Brackets

    题意: 给一匹配的括号序列,要求每对括号恰有一个被染成蓝或红色,要求相邻的括号不同色,求方案数. 解法: 类比树的hash将括号序列转化为一棵树,树上子节点之间不得出现冲突, 子节点和父节点不得出现冲 ...

  7. Java编程环境eclipse配置

    一. 下载并安装JDK https://www.cnblogs.com/zhangchao0515/p/6806408.html 二.下载并解压Eclipse https://www.cnblogs. ...

  8. oracle的日期数据类型

    https://blog.csdn.net/qq_33573235/article/details/78154928(转)

  9. c# 中的 protected internal 如何在 vc.net 中实现

    c# 中有 protected internal 的复合访问属性, 保证assembly内部访问,以及外部的派生类访问 vc.net 中无法直接写上 protected internal, 其对应的写 ...

  10. [Xcode 实际操作]九、实用进阶-(3)给代码方法添加宏注释

    目录:[Swift]Xcode实际操作 本文将演示如何在方法列表中,对方法名称进行注释. 这样可以使程序,按功能分块,使方法清晰.易读并且方便定位. 在项目导航区,打开视图控制器的代码文件[ViewC ...