JDK1.8 ConcurrentHashMap源码细致分解01

2023-11-25 08:10

本文主要是介绍JDK1.8 ConcurrentHashMap源码细致分解01,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章参考:小刘讲源码

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

1、简介

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

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

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

  1. 如果桶数组未初始化,则初始化;
  2. 如果待插入的元素所在的桶为空,则尝试把此元素直接插入都桶中的第一个位置;
  3. 如果正在扩容,则当前线程一起加入到扩容的过程中;
  4. 如果待插入的元素所在的桶不为空且不存在迁移的元素,则锁住这个桶(分段锁)
  5. 如果当前桶中元素以链表方式存储,则在链表中寻找该元素或者插入元素;
  6. 如果当前桶中元素以红黑树方式存储,则在红黑树中寻找该元素或者插入元素;
  7. 如果元素存在,则返回旧值
  8. 如果元素不存在,整个 Map 的元素加 1,并检查是否需要扩容;

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

为什么使用 synchronized 而不是 ReentrantLock?

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

2、JDK1.8 ConcurrentHashMap 结构图

7JmSCq.png

3、成员属性

/*** 散列表数组最大限制*/
private static final int MAXIMUM_CAPACITY = 1 << 30;/*** 散列表默认值*/
private static final int DEFAULT_CAPACITY = 16;/*** 支持的数组最大长度,不少MAX_INTEGER_VALUE*/
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/*** 并发级别:JDK1.7遗留下来的,JDK1.8只有在初始化的时候用了一用,并不代表并发级别*/
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;/*** 负载因子,JDK1.8中,ConcurrentHashMap是固定值*/
private static final float LOAD_FACTOR = 0.75f;/*** 树化阙值,指定桶位  链表长度达到8的话,有可能发生树化操作*/
static final int TREEIFY_THRESHOLD = 8;/*** 红黑树转化为链表的阙值*/
static final int UNTREEIFY_THRESHOLD = 6;/*** 联合TREEIFY_THRESHOLD控制桶位是否树化条件,只有当table数组长度达到64且某个桶位中的链表长度达到8才会真正树化*/
static final int MIN_TREEIFY_CAPACITY = 64;/*** 线程迁移数据最小步长,控制线程迁移任务最小区间的一个值*/
private static final int MIN_TRANSFER_STRIDE = 16;/*** 扩容相关,计算扩容时生成的一个标识戳*/
private static int RESIZE_STAMP_BITS = 16;/*** 65535,表示并发扩容最多线程数*/
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;/*** 扩容相关*/
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;/** Encodings for Node hash fields. See above for explanation.*/
// 当node节点的hash值为-1的时候,表示当前节点是FWD节点
static final int MOVED     = -1; // hash for forwarding nodes
// 当node节点的hash值是-2的时候,表示当前节点已经树化了且当前节点为TreeBin对象,TreeBin对象代理操作红黑树
static final int TREEBIN   = -2; // hash for roots of trees
static final int RESERVED  = -3; // hash for transient reservations
// 0x7fffffff => 0111 1111 1111 1111 1111 1111 1111 1111 可以将一个负数通过位与运算后得到正数,但是不少取绝对值
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash// 当前系统的CPU数量
static final int NCPU = Runtime.getRuntime().availableProcessors();// 散列表对象,长度一定是2的次方数
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;/*** sizeCtl < 0* 1.-1的时候表示当前的table正在初始化(有线程正在创建table数组)当前线程需要自旋等待* 2.表示当前map正在进行扩容,高16位表示:扩容的标识戳 低16位表示:(1 + nThread)表示当前参与并发扩容的线程数量** sizeCtl == 0 表示创建table数组的时候,使用DEFAULT_CAPACITY 为大小** sizeCtl > 0* 1. 如果table未初始化,表示初始化大小* 2. 如果table已经初始化,表示下次扩容时的触发条件(阙值)*/
private transient volatile int sizeCtl;// 扩容过程中,记录当前进度,所有线程都需要从transferIndex中分配区间任务,去执行自己的任务
private transient volatile int transferIndex;// LongAdder 中的cellsBusy 0表示当前LongAdder对象无锁状态,1表示处于加锁状态
private transient volatile int cellsBusy;// LongAdder 中的cells数组,当baseCount发生竞争后,会创建cells数组
// 多线程情况下,线程会通过计算hash值取到自己的cell位置,将增量累加到cell中
// 总个数 = sum(cells) + baseCount
private transient volatile CounterCell[] counterCells;

4、静态属性

// Unsafe mechanics
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属性在CounterCell中的内存偏移地址
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> {final int hash;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;}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();}// 这里equals是以Entry节点来进行比较访问的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 中的 thresoldloadFactor,而是改用了 sizaCtl 来控制,而且只存储了容量里面,那么它是怎么用的呢?官方给出的解释如下:

  • -1,表示有线程正在进行初始化操作。
  • -(1 + nThreads),表示有 n 个线程正在一起扩容。
  • 0,默认值,后续再真正初始化的时候使用默认容量。
  • > 0,初始化或者扩容完成后下一次的扩容门槛。

8、内部小方法分析

下面在正式分析并发 HashMap 成员方法之前,先分析一些内部类中的子方法函数:

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

static class Node<K,V> implements Map.Entry<K,V> {final int hash;// 该属性是通过spread(int h)方法计算得到~ h 代表keyfinal 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;}...
}

8.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_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 节点。

8.2、tabAt(Node<K,V>[] tab, int i) 方法

/*** 获取 tab(Node[]) 数组中指定下标位置 i 的Node元素* Node<K,V> tab:表示Node[]数组* int i:表示数组下标*/
@SuppressWarnings("unchecked")
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 的时候,先复习一下上面所说的介绍的一些静态属性字段的含义:

// Node 数组的Class 对象
Class<?> ak = Node[].class;
// U.arrayBaseOffset(ak):根据ak 获取NOde[] 数组第一个元素的偏移地址
ABASE = U.arrayBaseOffset(ak);
// 表示数组单元所占用空间大小,scale表示Node[]数组中每一个单元所占用的空间大小
int scale = U.arrayIndexScale(ak);
// 在java语言中scale必须是二的次方数,这里就是一个小算法判断scale是不是二的次方数
// 如果不是则报错
if ((scale & (scale - 1)) != 0)throw new Error("data type scale not a power of two");
// numberOfLeadingZeros(scale):根据scale,返回的当前数值转化位二进制后,这里注意(scale的值一定是2的次幂数),从那个高位到低位开始统计,统计有多少个0连续在一块
// eg:8 -> 1000 则numberOfLeadingZeros(8) 的值就是28,为什么呢?因为Integer是32位,1000占4位,那么前面就有32-4个0,即连续最长的0的个数为28个
// 4-> 100,则numberOfLeadingZeros(4)的值就是29
// ASHIFT = 31 - Integer.numberOfLeadingZeros(4) = 2,那么ASHIFT的作用是什么呢?其实它有一个数组寻址的一个作用:
// 拿到下标为5的Node[] 数组元素的偏移地址(存储地址):假设此时根据scale计算得到的ASHIFT = 2
// ASHIFT主要是用于内存寻址的时使用,假如我们要寻找第6桶位的地址,ABSE + 5 * scale
// 转化为 ABASE + (5 << ASHIFT)
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);

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

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

8.3、casTabAt(Node<K,V>[] tab, int i) 方法

/*** 通过 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);
}

8.4、setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)方法

/*** 根据数组下标,设置 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);
}

8.5、resizeStamp(int n) 方法

// 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
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  ---> 计算得到扩容标识戳

8.6、tableSizeFor(int c) 方法

/*** 返回大于等于 c 的最小的2的次方数* eg: c = 28* n = c - 1 = 27 -> 0b11011* n |= n >>> 1* n => 11011 |= 01101 => 11111* n |= n >>> 2* n => 11111 |= 00111 => 11111* ...* => 1111 + 1 = 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;
}

8.7、构造方法

// 无参构造方法
public ConcurrentHashMap() {
}// 指定初始化容量
public ConcurrentHashMap(int initialCapacity) {// 判断给定的数组初始长度是否小于0,小于0的话直接抛出异常if (initialCapacity < 0)throw new IllegalArgumentException();int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?MAXIMUM_CAPACITY :tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));// sizeCtl>0// 当size初始化的时候,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)   // Use at least as many bins// 初始化容量的值设置为并发级别的值// 即,JDK1.8之后并发级别由散列表长度决定initialCapacity = concurrencyLevel;   // as estimated threads// 根据初始化容量和负载因子,去计算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;
}

至此,ConcurrentHashMap 的源码分析准备工作就完成了,之后会更新其中一些较为麻烦的方法。

这篇关于JDK1.8 ConcurrentHashMap源码细致分解01的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/423421

相关文章

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Centos7安装JDK1.8保姆版

工欲善其事,必先利其器。这句话同样适用于学习Java编程。在开始Java的学习旅程之前,我们必须首先配置好适合的开发环境。 通过事先准备好这些工具和配置,我们可以避免在学习过程中遇到因环境问题导致的代码异常或错误。一个稳定、高效的开发环境能够让我们更加专注于代码的学习和编写,提升学习效率,减少不必要的困扰和挫折感。因此,在学习Java之初,投入一些时间和精力来配置好开发环境是非常值得的。这将为我

hdu 2602 and poj 3624(01背包)

01背包的模板题。 hdu2602代码: #include<stdio.h>#include<string.h>const int MaxN = 1001;int max(int a, int b){return a > b ? a : b;}int w[MaxN];int v[MaxN];int dp[MaxN];int main(){int T;int N, V;s

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

Spring 源码解读:自定义实现Bean定义的注册与解析

引言 在Spring框架中,Bean的注册与解析是整个依赖注入流程的核心步骤。通过Bean定义,Spring容器知道如何创建、配置和管理每个Bean实例。本篇文章将通过实现一个简化版的Bean定义注册与解析机制,帮助你理解Spring框架背后的设计逻辑。我们还将对比Spring中的BeanDefinition和BeanDefinitionRegistry,以全面掌握Bean注册和解析的核心原理。

集中式版本控制与分布式版本控制——Git 学习笔记01

什么是版本控制 如果你用 Microsoft Word 写过东西,那你八成会有这样的经历: 想删除一段文字,又怕将来这段文字有用,怎么办呢?有一个办法,先把当前文件“另存为”一个文件,然后继续改,改到某个程度,再“另存为”一个文件。就这样改着、存着……最后你的 Word 文档变成了这样: 过了几天,你想找回被删除的文字,但是已经记不清保存在哪个文件了,只能挨个去找。真麻烦,眼睛都花了。看