上一篇文章简单的写了一下,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. bootstrap学习大纲

    bootstrap 学习分三部分,分别是 css样式,css组件,js插件. 下面介绍三部分分别要学习的内容: 1.css样式:栅格系统,排版,代码,表格,表单,按钮,图片,辅助类,响应式工具. 2. ...

  2. bzoj 2155 Xor

    题目大意: 求一条从$1 \rightarrow n$的路径是异或和最大 思路: 先随便求一棵生成树,然后求出所有环,对于所有环都可以去转一圈只取到这个环的贡献 那么就是线性基裸题了 #include ...

  3. js正则匹配字符串

    这里我第一时间想到的就是用 js 的search 和 match ,其中最常见的是match: 1. str.search(regexp):search()方法不支持全局搜索,因为会忽略正则表达式参数 ...

  4. React-Native 基本环境的搭建

    看了一些RN资料,仅仅了解到人们对 RN(以下简称 React_native 框架) 的使用描述以及评价,就觉得RN是一个很不错的框架,值得学习.今天就开始写我的学习记录,也给大家分享一下.下面进入正 ...

  5. IReport制作报表——日期时间显示格式

    转自:https://blog.csdn.net/linglinglu/article/details/9022679?utm_source=blogxgwz2 IReport工具在制作报表的时候,会 ...

  6. android系统的源代码获取(亲测可用)

    1.在线阅读各版本源代码: http://androidxref.com/ 2.下载到本地: http://blog.csdn.net/yin1031468524/article/details/55 ...

  7. python-os.walk()使用举例

    文件目录结构 dir 1 1 1.txt 2.txt 3.txt 2 2.txt 3 4 4.txt 3.txt 1.txt 2 2.txt 3 3.txt dir.txt 代码: import os ...

  8. sql添加表

    IF EXISTS(SELECT * FROM sysobjects WHERE name='learnRecord') DROP TABLE learnRecord GO CREATE TABLE ...

  9. 混用ngui和ugui渲染顺序问题

    http://blog.csdn.net/xtxy/article/details/38332801 为NGUI panel 添加 sorting layer 接着上一篇文章的问题,看到了老外做的一个 ...

  10. [Xcode 实际操作]二、视图与手势-(2)UIView视图的层次关系

    目录:[Swift]Xcode实际操作 本文将演示创建三个视图对象,其中第二个视图是第三个视图的父视图. 现在开始编写代码,实现这项功能 import UIKit class ViewControll ...