文章参考:小刘源码

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

1、简介

  • ConcurrentHashMap是HashMap的线程安全版本,内部也是使用(数组 + 链表 + 红黑树)的结构来存储元素。相比于同样线程安全的HashTable来说,效率等各方面都有极大地提高。
  • 在学习ConcurrentHashMap源码之前,这里默认大家已经读过HashMap源码,了解LongAdder原子类、红黑树。参考:JDK集合源码之HashMap解析(上)、JDK集合源码之HashMap解析(下)、HashMap底层红黑树实现(自己实现一个简单的红黑树)、LongAdder源码解析。

先简单介绍下ConcurrentHashMap的整体流程:

整体流程跟HashMap比较类似,大致是以下几步:

(1)如果桶数组未初始化,则初始化;

(2)如果待插入的元素所在的桶为空,则尝试把此元素直接插入到桶的第一个位置;

(3)如果正在扩容,则当前线程一起加入到扩容的过程中;

(4)如果待插入的元素所在的桶不为空且不在迁移元素,则锁住这个桶(分段锁);

(5)如果当前桶中元素以链表方式存储,则在链表中寻找该元素或者插入元素;

(6)如果当前桶中元素以红黑树方式存储,则在红黑树中寻找该元素或者插入元素;

(7)如果元素存在,则返回旧值;

(8)如果元素不存在,整个Map的元素个数加1,并检查是否需要扩容;

添加元素操作中使用的锁主要有(自旋锁 + CAS + synchronized + 分段锁)。

为什么使用synchronized而不是ReentrantLock?

因为synchronized已经得到了极大地优化,在特定情况下并不比ReentrantLock差。

2、JDK1.8 ConcurrentHashMap结构图

3、成员属性

// 散列表数组最大容量值
private static final int MAXIMUM_CAPACITY = 1 << 30;// 散列表默认容量值16
private static final int DEFAULT_CAPACITY = 16;// 最大的数组大小(非2的幂) toArray和相关方法需要(并不是核心属性)
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;// jdk1.7遗留下来的,用来表示并发级别的属性
// jdk1.8只有在初始化的时候用到,不再表示并发级别了~ 1.8以后并发级别由散列表长度决定
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;// 负载因子:表示散列表的填满程度~ 在ConcurrentHashMap中,该属性是固定值0.75,不可修改~
private static final float LOAD_FACTOR = 0.75f;// 树化阈值:散列表的一个桶中链表长度达到8时候,可能发生链表树化
static final int TREEIFY_THRESHOLD = 8;// 反树化阈值:散列表的一个桶中的红黑树元素个数小于6时候,将红黑树转换回链表结构
static final int UNTREEIFY_THRESHOLD = 6;// 散列表长度达到64,且某个桶位中的链表长度达到8,才会发生树化
static final int MIN_TREEIFY_CAPACITY = 64;// 控制线程迁移数据的最小步长(桶位的跨度~)
private static final int MIN_TRANSFER_STRIDE = 16;// 固定值16,与扩容相关,计算扩容时会根据该属性值生成一个扩容标识戳
private static int RESIZE_STAMP_BITS = 16;// (1 << (32 - RESIZE_STAMP_BITS)) - 1 = 65535:1 << 16 -1
// 表示并发扩容最多容纳的线程数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;// 也是扩容相关属性,在扩容分析的时候会用到~
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;// 当node节点的hash值为-1:表示当前节点是FWD(forwarding)节点(已经被迁移的节点)
static final int MOVED     = -1;
// 当node节点的hash值为-2:表示当前节点已经树化,且当前节点为TreeBin对象~,TreeBin对象代理操作红黑树
static final int TREEBIN   = -2;
// 当node节点的hash值为-3:
static final int RESERVED  = -3;
// 0x7fffffff 十六进制转二进制值为:1111111111111111111111111111111(31个1)
// 作用是将一个二进制负数与1111111111111111111111111111111 进行按位与(&)运算时,会得到一个正数,但不是取绝对值
static final int HASH_BITS = 0x7fffffff; // 当前系统的CPU数量
static final int NCPU = Runtime.getRuntime().availableProcessors();// JDK1.8 序列化为了兼容 JDK1.7的ConcurrentHashMap用到的属性 (非核心属性)
private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField("segments", Segment[].class),new ObjectStreamField("segmentMask", Integer.TYPE),new ObjectStreamField("segmentShift", Integer.TYPE)
};// 散列表table
transient volatile Node<K,V>[] table;// 新表的引用:扩容过程中,会将扩容中的新table赋值给nextTable,(保持引用),扩容结束之后,这里就会被设置为NULL
private transient volatile Node<K,V>[] nextTable;// 与LongAdder中的baseCount作用相同: 当未发生线程竞争或当前LongAdder处于加锁状态时,增量会被累加到baseCount
private transient volatile long baseCount;// 表示散列表table的状态:
// sizeCtl<0时:
// 情况一、sizeCtl=-1: 表示当前table正在进行初始化(即,有线程在创建table数组),当前线程需要自旋等待...
// 情况二、表示当前table散列表正在进行扩容,高16位表示扩容的标识戳,低16位表示扩容线程数:(1 + nThread) 即,当前参与并发扩容的线程数量。
// sizeCtl=0时:表示创建table散列表时,使用默认初始容量DEFAULT_CAPACITY=16
// sizeCtl>0时:
// 情况一、如果table未初始化,表示初始化大小
// 情况二、如果table已经初始化,表示下次扩容时,触发条件(阈值)
private transient volatile int sizeCtl;// 扩容过程中,记录当前进度。所有的线程都需要从transferIndex中分配区间任务,并去执行自己的任务
private transient volatile int transferIndex;// LongAdder中,cellsBusy表示对象的加锁状态:
// 0: 表示当前LongAdder对象处于无锁状态
// 1: 表示当前LongAdder对象处于加锁状态
private transient volatile int cellsBusy;// LongAdder中的cells数组,当baseCount发生线程竞争后,会创建cells数组,
// 线程会通过计算hash值,去取到自己的cell,将增量累加到指定的cell中
// 总数 = sum(cells) + baseCount
private transient volatile CounterCell[] counterCells;

4、静态属性

// Unsafe 类
private static final sun.misc.Unsafe U;
// 表示sizeCtl属性在ConcurrentHashMap中内存的偏移地址
private static final long SIZECTL;
// 表示transferIndex属性在ConcurrentHashMap中内存的偏移地址
private static final long TRANSFERINDEX;
// 表示baseCount属性在ConcurrentHashMap中内存的偏移地址
private static final long BASECOUNT;
// 表示cellsBusy属性在ConcurrentHashMap中内存的偏移地址
private static final long CELLSBUSY;
// 表示cellsValue属性在ConcurrentHashMap中内存的偏移地址
private static final long CELLVALUE;
// 表示数组第一个元素的偏移地址
private static final long ABASE;
// 该属性用于数组寻址,请继续往下阅读
private static final int ASHIFT;

5、静态代码块

static {try {U = sun.misc.Unsafe.getUnsafe();Class<?> k = ConcurrentHashMap.class;SIZECTL = U.objectFieldOffset(k.getDeclaredField("sizeCtl"));TRANSFERINDEX = U.objectFieldOffset(k.getDeclaredField("transferIndex"));BASECOUNT = U.objectFieldOffset(k.getDeclaredField("baseCount"));CELLSBUSY = U.objectFieldOffset(k.getDeclaredField("cellsBusy"));Class<?> ck = CounterCell.class;CELLVALUE = U.objectFieldOffset(ck.getDeclaredField("value"));Class<?> ak = Node[].class;// 拿到数组第一个元素的偏移地址ABASE = U.arrayBaseOffset(ak);// 表示数组中每一个单元所占用的空间大小,即scale表示Node[]数组中每一个单元所占用的空间int scale = U.arrayIndexScale(ak);// (scale & (scale - 1)) != 0:判断scale的数值是否是2的次幂数// java语言规范中,要求数组中计算出的scale必须为2的次幂数// 1 0000 % 0 1111 = 0if ((scale & (scale - 1)) != 0)throw new Error("data type scale not a power of two");// 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);} catch (Exception e) {throw new Error(e);}
}

6、内部类

6.1 Node节点

static class Node<K,V> implements Map.Entry<K,V> {// hash值final int hash;// keyfinal K key;// valuevolatile 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;}public final K getKey()       { return key; }public final V getValue()     { return val; }public final int hashCode()   { return key.hashCode() ^ val.hashCode(); public final String toString(){ return key + "=" + val; }public final V setValue(V value) {throw new UnsupportedOperationException();}public final boolean equals(Object o) {Object k, v, u; Map.Entry<?,?> e;return ((o instanceof Map.Entry) &&(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&(v = e.getValue()) != null &&(k == key || k.equals(key)) &&(v == (u = val) || v.equals(u)));}/*** Virtualized support for map.get(); overridden in subclasses.*/Node<K,V> find(int h, Object k) {Node<K,V> e = this;if (k != null) {do {K ek;if (e.hash == h &&((ek = e.key) == k || (ek != null && k.equals(ek))))return e;} while ((e = e.next) != null);}return null;}
}

6.2 ForwardingNode节点

这个内部类在之后分析扩容的文章中会再仔细去探究,这里先熟悉一下~

// 如果是一个写的线程(eg:并发扩容线程),则需要为创建新表贡献一份力
// 如果是一个读的线程,则调用该内部类的find(int h, Object k)方法
static final class ForwardingNode<K,V> extends Node<K,V> {// nextTable表示新散列表的引用final Node<K,V>[] nextTable;ForwardingNode(Node<K,V>[] tab) {super(MOVED, null, null, null);this.nextTable = tab;}// 到新表上去读数据Node<K,V> find(int h, Object k) {// loop to avoid arbitrarily deep recursion on forwarding nodesouter: for (Node<K,V>[] tab = nextTable;;) {Node<K,V> e; int n;if (k == null || tab == null || (n = tab.length) == 0 ||(e = tabAt(tab, (n - 1) & h)) == null)return null;for (;;) {int eh; K ek;if ((eh = e.hash) == h &&((ek = e.key) == k || (ek != null && k.equals(ek))))return e;if (eh < 0) {if (e instanceof ForwardingNode) {tab = ((ForwardingNode<K,V>)e).nextTable;continue outer;}elsereturn e.find(h, k);}if ((e = e.next) == null)return null;}}}
}

6.3 TreeNode节点

TreeBin中需要用到该节点,之后会细说~

static final class TreeNode<K,V> extends Node<K,V> {// 父节点TreeNode<K,V> parent;  // red-black tree links// 左子节点TreeNode<K,V> left;// 右节点TreeNode<K,V> right;// 前驱节点TreeNode<K,V> prev;    // needed to unlink next upon deletion// 节点有红、黑两种颜色~boolean red;TreeNode(int hash, K key, V val, Node<K,V> next,TreeNode<K,V> parent) {super(hash, key, val, next);this.parent = parent;}Node<K,V> find(int h, Object k) {return findTreeNode(h, k, null);}/*** Returns the TreeNode (or null if not found) for the given key* starting at given root.*/final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {if (k != null) {TreeNode<K,V> p = this;do  {int ph, dir; K pk; TreeNode<K,V> q;TreeNode<K,V> pl = p.left, pr = p.right;if ((ph = p.hash) > h)p = pl;else if (ph < h)p = pr;else if ((pk = p.key) == k || (pk != null && k.equals(pk)))return p;else if (pl == null)p = pr;else if (pr == null)p = pl;else if ((kc != null ||(kc = comparableClassFor(k)) != null) &&(dir = compareComparables(kc, k, pk)) != 0)p = (dir < 0) ? pl : pr;else if ((q = pr.findTreeNode(h, k, kc)) != null)return q;elsep = pl;} while (p != null);}return null;}
}

7、构造方法

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));this.sizeCtl = cap;
}public ConcurrentHashMap(Map<? extends K, ? extends V> m) {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();if (initialCapacity < concurrencyLevel)   // Use at least as many binsinitialCapacity = concurrencyLevel;   // as estimated threadslong size = (long)(1.0 + (long)initialCapacity / loadFactor);int cap = (size >= (long)MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : tableSizeFor((int)size);this.sizeCtl = cap;
}

构造方法与HashMap对比可以发现,没有了HashMap中的thresholdloadFactor,而是改用了sizeCtl来控制,而且只存储了容量在里面,那么它是怎么用的呢?官方给出的解释如下:

(1)-1,表示有线程正在进行初始化操作。

(2)-(1 + nThreads),表示有n个线程正在一起扩容。

(3)0,默认值,后续在真正初始化的时候使用默认容量。

(4)> 0,初始化或扩容完成后下一次的扩容门槛 。

ConcurrentHashMap源码解析_01 成员属性、内部类、构造方法分析相关推荐

  1. ConcurrentHashMap源码解析_02 预热(内部一些小方法分析)

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

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

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

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

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

  4. 4、Eureka 源码解析 之 Eureka Client 启动原理分析

    在前面的一篇文章 3.Eureka 源码解析 之 Eureka Server 启动原理分析当中我们分析了一下 Eureka Server 的启动.在集群环境下 Eureka Server 相互之前需要 ...

  5. ConcurrentHashMap源码解析(1)

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

  6. hashmap与concurrenthashmap源码解析

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

  7. JDK1.8 ConcurrentHashMap 源码解析

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

  8. Spring源码解析(七)-Bean属性间的循环依赖

    首先复习一下前面学习的Spring容器启动的大致流程,首先Spring会先扫描所有需要实例化的Bean,将这些Bean的信息封装成一个个BeanDefinition,然后注册到BeanDefiniti ...

  9. JDK8中ConcurrentHashMap源码解析

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

最新文章

  1. 最全Windows下搭建go语言开发环境以及开发IDE
  2. Java Android 32位16位 MD5加密
  3. 不是内部或外部命令也不是可运行的程序?
  4. 存储过程语法 - 变量
  5. j控制frameset修改cols,页面的隐藏和打开。
  6. 信息学奥赛一本通(1025:保留12位小数的浮点数)
  7. leetcode题库:6. Z字形变换
  8. Unity 游戏框架搭建 (五) 简易消息机制
  9. pku1159 Palindrome(简单dp)
  10. mysql成绩统计数据库设计_MySQL数据库设计——以学生成绩数据库为例
  11. python deap_看完必会的 python DEAP遗传算法库讲解+实战(中级)
  12. 深度学习教程(3) | 浅层神经网络(吴恩达·完整版)
  13. energy plus matlab,EnergyPlus EMS和ExternalInterface入门学习 [第一篇——简介]
  14. swotppt模板_swot分析ppt(最好swot模板)20285.ppt
  15. fast路由器服务器未响应,迅捷(FAST)无线路由器设置好了上不了网的解决方法
  16. Android入门篇
  17. MFC里面的一些实例应用
  18. 学java被“劝退”的第九天
  19. python获取股票的市盈率_怎样查找股票的历史市盈率数据?
  20. GS1011无线模块的使用简介。

热门文章

  1. windows录屏投屏,拓展屏投屏
  2. ​​​​​​​测试用例编写指南
  3. 计算机基础ppt操作试题,MS Office试题:PPT的基础操作题
  4. 【安卓开发】数据库Room框架的学习和使用
  5. fmincon c语言 编译,fmincon函数c(x)非线性函数求解
  6. MFC设置背景图片之三(使用GDI+)
  7. IPTV 和OTT TV 理解比较和应用
  8. 剑指offer刷题1-66题-Python3
  9. AC220转DC5V输出
  10. 验证码短信平台短信发送失败怎么办?