前面一篇文章中介绍了并发HashMap的主要成员属性,内部类和构造函数,下面在正式分析并发HashMap成员方法之前,先分析一些内部类中的字方法函数:

首先来看下ConcurrentHashMap内部类Node中的hash成员属性值的计算方法spread(int h)

static class Node<K,V> implements Map.Entry<K,V> {final int hash;// 该属性是通过spread(int h)方法计算得到~final K key;volatile V val;volatile Node<K,V> next;Node(int hash, K key, V val, Node<K,V> next) {this.hash = hash;this.key = key;this.val = val;this.next = next;}...
}

1、spread(int h)方法

/*** 计算Node节点hash值的算法:参数h为hash值* eg: * h二进制为 -->                    1100 0011 1010 0101 0001 1100 0001 1110* (h >>> 16) -->                    0000 0000 0000 0000 1100 0011 1010 0101 * (h ^ (h >>> 16)) -->              1100 0011 1010 0101 1101 1111 1011 1011* 注:(h ^ (h >>> 16)) 目的是让h的高16位也参与寻址计算,使得到的hash值更分散,减少hash冲突产生~* ------------------------------------------------------------------------------* HASH_BITS -->                  0111 1111 1111 1111 1111 1111 1111 1111* (h ^ (h >>> 16)) -->               1100 0011 1010 0101 1101 1111 1011 1011* (h ^ (h >>> 16)) & HASH_BITS --> 0100 0011 1010 0101 1101 1111 1011 1011* 注: (h ^ (h >>> 16))得到的结果再& HASH_BITS,目的是为了让得到的hash值结果始终是一个正数*/
static final int spread(int h) {// 让原来的hash值异或^原来hash值的右移16位,再与&上HASH_BITS(0x7fffffff: 二进制为31个1)return (h ^ (h >>> 16)) & HASH_BITS;
}

下面介绍tabAt(Node<K,V>[] tab, int i)方法:获取 tab(Node[]) 数组指定下标 i 的Node节点。

2、tabAt方法

/*** 获取 tab(Node[]) 数组指定下标 i 的Node节点* Node<K,V>[] tab:表示Node[]数组* int i:表示数组下标*/
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {// ((long)i << ASHIFT) + ABASE 的作用:请看下面的分析描述~return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}

在分析((long)i << ASHIFT) + ABASE时,先复习一下上一篇文章:ConcurrentHashMap源码解析_01 成员属性、内部类、构造方法分析中介绍到的一些属性字段的含义:

// Node数组的class对象
Class<?> ak = Node[].class;// U.arrayBaseOffset(ak):根据as获取Node[]数组第一个元素的偏移地址ABASE
ABASE = U.arrayBaseOffset(ak);// scale:表示数组中每一个单元所占用的空间大小,即scale表示Node[]数组中每一个单元所占用的空间
int scale = U.arrayIndexScale(ak);// scale必须为2的次幂数// numberOfLeadingZeros(scale) 根据scale,返回当前数值转换为二进制后,从高位到地位开始统计,统计有多少个0连续在一块:eg, 8转换二进制=>1000 则 numberOfLeadingZeros(8)的结果就是28,为什么呢?因为Integer是32位,1000占4位,那么前面就有32-4个0,即连续最长的0的个数为28个
// 4转换二进制=>100 则 numberOfLeadingZeros(8)的结果就是29
// ASHIFT = 31 - Integer.numberOfLeadingZeros(4) = 2 那么ASHIFT的作用是什么呢?其实它有数组寻址的一个作用:
// 拿到下标为5的Node[]数组元素的偏移地址(存储地址):假设此时 根据scale计算得到的ASHIFT = 2
// ABASE + (5 << ASHIFT) == ABASE + (5 << 2) == ABASE + 5 * scale(乘法运算效率低),就得到了下标为5的数组元素的偏移地址
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);

由上面的几个属性字段的复习介绍,不难得出:

  • ((long)i << ASHIFT) + ABASE 就是得到当前Node[] 数组下标为i的节点对象的偏移地址。
  • 然后再通过(Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE) 方法,根据Node[]目标节点Node的偏移地址两个参数,得到下标为i的Node节点对象。
  • 虽然这样很绕,不如,但是直接根据偏移地址去寻找数组元素效率较高~

3、casTabAt方法

/*** 通过CAS的方式去向Node数组指定位置i设置节点值,设置成功返回true,否则返回false* Node<K,V>[] tab:表示Node[]数组* int i:表示数组下标* Node<K,V> c:期望节点值* Node<K,V> v:要设置的节点值*/
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,Node<K,V> c, Node<K,V> v) {// 调用Unsafe的比较并交换去设置Node[]数组指定位置的节点值,参数如下:// tab:Node[]数组// ((long)i << ASHIFT) + ABASE:下标为i数组桶的偏移地址// c:期望节点值// v:要设置的节点值return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}

4、setTabAt方法

/*** 根据数组下标i,设置Node[]数组指定下标位置的节点值:* Node<K,V>[] tab:表示Node[]数组* int i:表示数组下标* Node<K,V> v:要设置的节点值*/
static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {// ((long)i << ASHIFT) + ABASE:下标为i数组桶的偏移地址U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
}

5、resizeStamp方法

/*** table数组扩容时,计算出一个扩容标识戳,当需要并发扩容时,当前线程必须拿到扩容标识戳才能参与扩容:*/
static final int resizeStamp(int n) {// RESIZE_STAMP_BITS:固定值16,与扩容相关,计算扩容时会根据该属性值生成一个扩容标识戳return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
}

举例子分析一下:

  • 当我们需要table容量从16 库容到32时:Integer.numberOfLeadingZeros(16) 会得到 27,怎么得来的呢?

    • numberOfLeadingZeros(n) 根据传入的n,返回当前数值转换为二进制后,从高位到地位开始统计,统计有多少个0连续在一块:

      • eg:16 转换二进制 => 1 0000numberOfLeadingZeros(16)的结果就是 27,因为Integer是32位,1 0000占5位,那么前面就有(32 - 5)个0,即连续最长的0的个数为27个。
  • (1 << (RESIZE_STAMP_BITS - 1)):其中RESIZE_STAMP_BITS是一个固定值16,与扩容相关,计算扩容时会根据该属性值生成一个扩容标识戳
  • 下面就来计算一下:
// 从16扩容到32
16 -> 32
// 用A表示:
numberOfLeadingZeros(16) => 1 0000 => 27 => 0000 0000 0001 1011
// 用B表示:
(1 << (RESIZE_STAMP_BITS - 1)) => (1 << (16 - 1) => 1000 0000 0000 0000 => 32768// A | B
Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1))
-----------------------------------------------------------------
0000 0000 0001 1011  ---> A
1000 0000 0000 0000  ---> B
-------------------  ---> | 按位或
1000 0000 0001 1011  ---> 计算得到扩容标识戳

6、tableSizeFor方法

/*** 根据c,计算得到大于等于c的,最小2的次幂数,该方法在HashMap源码中分析过~* eg:c = 28 ,则计算得到的返回结果为 32* c = 28 ==> n=27 => 0b 11011** 11011 | 01101 => 11111 ---- n |= n >>> 1* 11111 | 00111 => 11111 ---- n |= n >>> 2* ....* => 11111 + 1 = 100000 = 32*/
private static final int tableSizeFor(int c) {int n = c - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

7、构造方法(复习)

复习一下上一篇文章中的并发HashMap的狗构造方法:

// 无惨构造
public ConcurrentHashMap() {}// 指定初始化容量
public ConcurrentHashMap(int initialCapacity) {if (initialCapacity < 0)throw new IllegalArgumentException();int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?MAXIMUM_CAPACITY :tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));/*** sizeCtl > 0* 当目前table未初始化时,sizeCtl表示初始化容量*/this.sizeCtl = cap;
}// 根据一个Map集合来初始化
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {// sizeCtl设置为默认容量值this.sizeCtl = DEFAULT_CAPACITY;putAll(m);
}// 指定初始化容量,和负载因子
public ConcurrentHashMap(int initialCapacity, float loadFactor) {this(initialCapacity, loadFactor, 1);
}// 指定初始化容量,和负载因子,并发级别
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();// 当指定的初始化容量initialCapacity小于并发级别concurrencyLevel时if (initialCapacity < concurrencyLevel) // 初始化容量值设置为并发级别的值。// 即,JDK1.8以后并发级别由散列表长度决定initialCapacity = concurrencyLevel; // 根据初始化容量和负载因子,去计算sizelong size = (long)(1.0 + (long)initialCapacity / loadFactor);// 根据size重新计算数组初始化容量int cap = (size >= (long)MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : tableSizeFor((int)size);/*** sizeCtl > 0* 当目前table未初始化时,sizeCtl表示初始化容量*/this.sizeCtl = cap;
}

预热结束后,下一篇文章就是并发Map比较重点的地方了,即put()写入操作原理~

ConcurrentHashMap源码解析_02 预热(内部一些小方法分析)相关推荐

  1. 面试官系统精讲Java源码及大厂真题 - 16 ConcurrentHashMap 源码解析和设计思路

    16 ConcurrentHashMap 源码解析和设计思路 与有肝胆人共事,从无字句处读书. 引导语 当我们碰到线程不安全场景下,需要使用 Map 的时候,我们第一个想到的 API 估计就是 Con ...

  2. ConcurrentHashMap源码解析_01 成员属性、内部类、构造方法分析

    文章参考:小刘源码 ConcurrentHashMap源码解析_01 成员属性.内部类.构造方法分析 1.简介 ConcurrentHashMap是HashMap的线程安全版本,内部也是使用(数组 + ...

  3. ConcurrentHashMap源码解析——基于JDK1.8

    ConcurrentHashMap源码解析--基于JDK1.8 前言 这篇博客不知道写了多久,总之就是很久,头都炸了.最开始阅读源码时确实是一脸茫然,找不到下手的地方,真是太难了.下面的都是我自己阅读 ...

  4. YYCache 源码解析(一):使用方法,架构与内存缓存的设计

    YYCache是国内开发者ibireme开源的一个线程安全的高性能缓存组件,代码风格简洁清晰,阅读它的源码有助于建立比较完整的缓存设计的思路,同时也能巩固一下双向链表,线程锁,数据库操作相关的知识. ...

  5. hashmap与concurrenthashmap源码解析

    hashmap源码解析转载:http://www.cnblogs.com/ITtangtang/p/3948406.html 一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此 ...

  6. ConcurrentHashMap源码解析(1)

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

  7. JDK8中ConcurrentHashMap源码解析

    在介绍ConcurrentHashMap源码之前,首先需要了解以下几个知识 1.JDK1.8中ConcurrentHashMap的基本结构 2.并发编程的三个概念:可见性,原子性,有序性 3.CAS( ...

  8. 数据结构算法 - ConcurrentHashMap 源码解析

    Linux编程 点击右侧关注,免费入门到精通! 作者丨红橙Darren https://www.jianshu.com/p/0b452a6e4f4e 五个线程同时往 HashMap 中 put 数据会 ...

  9. JDK1.8 ConcurrentHashMap 源码解析

    概述 ConcurrentHashMap 是 util.concurrent 包的重要成员. ConcurrentHashMap 的源代码会涉及到散列算法,链表数据结构和红黑树 Java8 Concu ...

最新文章

  1. [原创]WildPackets Omnipeek介绍
  2. 【转载】安装程序无法复制文件CONVLOG.EX
  3. Python之定义默认参数
  4. html attr src,jQuery中css()和attr()方法的区别
  5. android动画实现单摆效果
  6. 【PyTorch】深度学习实践之 CNN基础篇——卷积神经网络跑Minst数据集
  7. QT学习回顾(二)界面布局及其控件设计
  8. 如何以静默方式将.REG文件添加到注册表
  9. DMZ区域的作用与原理
  10. 功能测试几种方法简单介绍
  11. qt 禁止alt+f4_禁止上下关闭按钮和Alt + F4
  12. python plt pyplot matplotlib绘图时形状异常
  13. JAVA 学习日志 测试抽象类的程序,每天进步/退步一点点,变化很大哦,努力!
  14. 快递e栈控制台版实现心得
  15. 昆虫大战电子计算机软件,风雨的昆虫大战AI双图版
  16. calcHist的使用
  17. 使用 Javascript 与 Flow 交互
  18. 【商业模式学习感悟】《B站:叛逃二次元和想破开的圈》读后感
  19. 一百行代码实现的HTML5登录页面
  20. cocos Uncaught Download text failed 错误解决

热门文章

  1. YYHS-蜀传之单刀赴会(梦回三国系列T2)(最短路+状压dp)
  2. 嘉立创PCB导出Gerber的方法(亲测有效)
  3. 2019牛客多校7H
  4. ROS与navigation——ACML参数配置
  5. 爬取google scholar
  6. Windows7环境32位操作系统安装Python
  7. c99什么意思_在c语言中=!是什么意思?
  8. 5G协议标准下载、关于5G的一些故事!
  9. 预期税收支出减少 苹果或将成美国税改最大赢家
  10. 刷脸移动考勤系统实时掌握学生考勤动态