jdk8 ConcurrentHashMap分析
- ConcurrentHashMap分析
- tryPresize()
- transfer()
- putVal()
- addCount()
- sumCount()
class ConcurrentHashMap
{
/**
* Tries to presize table to accommodate the given number of elements.
*
* @param size number of elements (doesn't need to be perfectly accurate)
*/
private final void tryPresize(int size) {
// 当size未达到MAXIMUM_CAPACITY时,扩容size。调用tableSizeFor(),
// 此处size + (size >>> 1) + 1 == size*1.5+1,注意, size = table.length << 1
// 传进来的size已经提前扩容了2倍
// c == 2**n
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
int sc;
// 判断条件,sizeCtl >=0 说明 table未初始化或处于正常状态,线程安全
while ((sc = sizeCtl) >= 0) {
Node<K,V>[] tab = table; int n;
// 如果使用put()方法,则会调用initTable(),初始化table[]。但是当直接调用putAll()方法时,
// 不会调用initTable()方法,因此在这里判断table是否为null
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
// CAS设置sizeCtl为-1, 线程安全
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 二次判断,线程安全
if (table == tab) {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
// 设置table[]
table = nt;
// 将sc=sizeClt设置为容量的75%, n - (n >>> 2) == n*0.75
sc = n - (n >>> 2);
}
} finally {
// 设置sizeCtl
sizeCtl = sc;
}
}
}
else if (c <= sc || n >= MAXIMUM_CAPACITY)
break;
else if (tab == table) {
int rs = resizeStamp(n);
// sc < 0 说明正在扩容,因此多线程辅助扩容
if (sc < 0) {
Node<K,V>[] nt;
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
// sc + 1,表示此线程正在辅助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
// 否则开始新的扩容
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
}
}
}
/**
* Moves and/or copies the nodes in each bin to new table. See
* above for explanation.
* 把数组中的节点复制到新的数组的相同位置,或者移动到扩张部分的相同位置
* 在这里首先会计算一个步长,表示一个线程处理的数组长度,用来控制对CPU的使用,
* 每个CPU最少处理16个长度的数组元素,也就是说,如果一个数组的长度只有16,那只有一个线程会对其进行扩容的复制移动操作
* 扩容的时候会一直遍历,知道复制完所有节点,没处理一个节点的时候会在链表的头部设置一个fwd节点,这样其他线程就会跳过他,
* 复制后在新数组中的链表不是绝对的反序的
*/
private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
int n = tab.length, stride;
// CPU的可用线程数,例如4核8线程,NCPU = 8
// MIN_TRANSFER_STRIDE: 每个线程的处理的数组的长度,最小为16
// n: 数组长度
// 当NCPU <= 1,也就是单核单线程时,stride=数组长度,也就是只有1个线程来扩容
// 当NCPU > 1时, 当stride<16时,直接将stride赋值为16。以8个线程的CPU举例,
// 只有当 table.length > 16(最小处理数组长度)*8(线程数)*8 时,才能改变stride的值(16)
// stride设置最小值,防止在容量很小的时候就使用大量的线程进行扩容
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE; // subdivide range
// 当nextTab == null, 说明这是第一个进行扩容的线程
if (nextTab == null) { // initiating
try {
@SuppressWarnings("unchecked")
// n << 1, 说明nextTab大小为原来的两倍
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
// transferIndex为扩容复制过程中的桶首节点遍历索引
// 从n开始,表示从后向前遍历
transferIndex = n;
}
int nextn = nextTab.length;
// ForwardingNode是Node节点的直接子类,是扩容过程中的特殊桶首节点
// 该类中没有key,value,next,hash值为特定的-1
// 同时具有一个Node和一个Node[]
// 创建一个fwd节点,这个是用来控制并发的,当一个节点为空或已经被转移之后,就设置为fwd节点
// 这是一个标志节点,指向nextTable
// 在扩容时才会出现的特殊节点,其key,value全部为null。并拥有nextTable指针引用新的table数组。
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab
// 死循环, i 表示数组下标,bound 表示当前线程可以处理的当前桶区间最小下标
// 例子,当 transferIndex = 64, stride = 16时,一次循环后
// i = 63, bound = transferIndex - stride = 48
for (int i = 0, bound = 0;;) {
Node<K,V> f; int fh;
while (advance) {
int nextIndex, nextBound;
// 注意: table[]处理区间的分配方式为从后往前
// --i >= bound 说明当前线程已经领取了要处理的table[]区间,并且还没有处理完毕,推出while循环进行处理
// finishing 说明所有的区间都已经处理完毕
if (--i >= bound || finishing)
advance = false;
// transferIndex 说明所有的区间都已经处理完毕
else if ((nextIndex = transferIndex) <= 0) {
i = -1;
advance = false;
}
// CAS 修改 transferIndex,即 length - 区间值,留下剩余的区间值供后面的线程使用
else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
// 当扩容完成时
// i<0 说明已经遍历完旧的数组tab
// i>=n 下方赋值 i=n
// nextn = nextTable.length,i+n>=nextn说明已经扩容完成
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
// 扩容完成
if (finishing) {
nextTable = null;
// 将nextTab赋值给table
table = nextTab;
// sizeCtl = 2n - 0.5n = 1.5n = 2*oldSizeCtl
sizeCtl = (n << 1) - (n >>> 1);
return;
}
// 线程退出 sizeCtl-1
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
// 当有多个线程执行完毕退出时,保证只有最后一个退出的线程可以继续执行,其他线程直接return
// 第一个扩容的线程,执行transfer方法之前,会设置 sizeCtl =
// (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2)
// 后续帮其扩容的线程,执行transfer方法之前,会设置 sizeCtl = sizeCtl+1
// 每一个退出transfer的方法的线程,退出之前,会设置 sizeCtl = sizeCtl-1
// 那么最后一个线程退出时:
// 必然有sc == (resizeStamp(n) << RESIZE_STAMP_SHIFT) + 2),
// 即 (sc - 2) == resizeStamp(n) << RESIZE_STAMP_SHIFT
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
// 最后退出的线程要重新check下是否全部迁移完毕
i = n; // recheck before commit
}
}
// 当扩容未完成时
// 当前数组中第i个元素为null,用CAS设置成特殊节点forwardingNode(可以理解成占位符)
// 表明这个节点已经处理过了
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
// 如果 f.hash=-1 的话说明该节点为 ForwardingNode,说明该节点已经处理过了
// 表示已经被其他线程处理了,则直接往前遍历,并发扩容的核心
else if ((fh = f.hash) == MOVED)
advance = true; // already processed
// 真正的扩容操作,锁住当前节点扩容,防止 putVal 的时候向链表插入数据
else {
// 锁住Node首节点
synchronized (f) {
// 二次判断,线程安全
if (tabAt(tab, i) == f) {
// ln低位桶,hn高位桶
Node<K,V> ln, hn;
// hash>0, 说明为普通Node节点
// TreeBin 的 hash 是 -2
if (fh >= 0) {
// n = table.length
// 因为n都是2的n次方,类似于00010000,因此根据bit为1的位进行与操作,
// 将Node分为两类,一类bit指定位=0,一类bit指定位=1
// 0&1=0, 1&1=1
// 当结果为0,将其放在低位;当结果为1,将其放在高位
// 这里高低位分配是有道理的,低位节点放在新链表的i位,高位节点放在新链表的i+n位,
// 这样对扩容后的新链表进行get操作时,hash&(n-1)仍能得到正确的位置,反之则不行
int runBit = fh & n;
// lastRun为当前节点链表的末尾
Node<K,V> lastRun = f;
// 将lastRun遍历到末尾,并设置runBit为末尾节点的runBit
for (Node<K,V> p = f.next; p != null; p = p.next) {
int b = p.hash & n;
// 当新的hash&n与runBit不同时,将新的hash&n作为runBit
// 并且将此节点作为lastRun。
// 这样做的目的是减少之后判断链表节点高低位的次数,因为当runBit相同时,
// 说明这些节点都会被放置同一高位或低位,因此当runBit不变时,说明后面的节点所属的位置相同
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
// 设为低位桶
if (runBit == 0) {
ln = lastRun;
hn = null;
}
// 设为高位桶
else {
hn = lastRun;
ln = null;
}
// 当桶位置为单节点时,此单节点会被放置高位或低位
// 当桶位置为链表时,此节点链表会被拆分为2个新链表
// 从首节点开始遍历,到lastRun尾节点,尾节点不遍历
for (Node<K,V> p = f; p != lastRun; p = p.next) {
// 获取hash, key, value
int ph = p.hash; K pk = p.key; V pv = p.val;
// 将原本的一个链表根据hash&n分为2个链表,构建新链表采用头插法
// 无法概括两个新链表相对旧链表的顺序,有很多可能,并不是一个正序,一个倒序
if ((ph & n) == 0)
ln = new Node<K,V>(ph, pk, pv, ln);
else
hn = new Node<K,V>(ph, pk, pv, hn);
}
// 将低位节点设置为新链表的i位
setTabAt(nextTab, i, ln);
// 将高位节点设置为新链表的i+n位
setTabAt(nextTab, i + n, hn);
// 将旧链表的i位设为ForwardingNode
setTabAt(tab, i, fwd);
// advance为true,所以当前线程可以重新领取扩容任务
advance = true;
}
// 树节点,红黑树迁移
else if (f instanceof TreeBin) {
TreeBin<K,V> t = (TreeBin<K,V>)f;
// 高低位树节点
TreeNode<K,V> lo = null, loTail = null;
TreeNode<K,V> hi = null, hiTail = null;
int lc = 0, hc = 0;
// 开始遍历
for (Node<K,V> e = t.first; e != null; e = e.next) {
// 构造树节点
int h = e.hash;
TreeNode<K,V> p = new TreeNode<K,V>
(h, e.key, e.val, null, null);
// 将构造好的节点放入低位树
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
}
// 将构造好的节点放入高位树
else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
// 将低位树头赋值给ln, 并判断要不要转为链表
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin<K,V>(lo) : t;
// 将高位树头赋值给hn, 并判断要不要转为链表
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin<K,V>(hi) : t;
// 将低位节点设置为新链表的i位
setTabAt(nextTab, i, ln);
// 将高位节点设置为新链表的i+n位
setTabAt(nextTab, i + n, hn);
// 将旧链表的i位设为ForwardingNode
setTabAt(tab, i, fwd);
// advance为true,所以当前线程可以重新领取扩容任务
advance = true;
}
}
}
}
}
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
// 得到节点Hash
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 第一次put先初始化table[],懒加载
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 如果put位置为空,通过CAS设置节点
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 如果put位置正在扩容,则当前线程加入辅助扩容
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// 如果put位置冲突,则锁住当前Node节点,线程安全
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
// hash>0,说明为Node节点
// 树节点的hash = -2
if (fh >= 0) {
binCount = 1;
// 遍历Node链表
for (Node<K,V> e = f;; ++binCount) {
K ek;
// hash值相同,覆盖value
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// hash值不同,将节点加入链表末尾
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 冲突节点为树节点
else if (f instanceof 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) {
// 当链表链表数量>8时,转为红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
// 增加Map计数
addCount(1L, binCount);
return null;
}
/**
* Adds to count, and if table is too small and not already
* resizing, initiates transfer. If already resizing, helps
* perform transfer if work is available. Rechecks occupancy
* after a transfer to see if another resize is already needed
* because resizings are lagging additions.
*
* @param x the count to add
* @param check if <0, don't check resize, if <= 1 only check if uncontended
* check, 当在putVal()中调用时,x = 1, check为节点链表长度
*/
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 当计数盒子不是空
// 当并发CAS修改baseCount失败时
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
// 如果计数盒子是空(尚未出现并发)
// 如果随机取余一个数组位置为空 或者
// 修改这个槽位的变量失败(出现并发了)
// 执行 fullAddCount 方法。并结束
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
// 获取Map中的元素个数
s = sumCount();
}
// 检查是否需要扩容
// check为节点链表长度
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
// 当元素个数>sizeCtl进行扩容
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
// rs = Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1))
// rs = n的最高非零位前面的0的个数 + 32768
// rs作为table长度的标识符
int rs = resizeStamp(n);
// sizeCtl<0,说明正在扩容中
if (sc < 0) {
// 不需要辅助扩容
// 如果 sc 的高16位左移后不等于 长度标识符(校验异常 sizeCtl 变化了)
// 如果 sc == 标识符 + 1 (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
// sc == rs + 1为BUG,见https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8214427
// 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
// 如果 nextTable == null(结束扩容了)
// 如果 transferIndex <= 0 (转移状态变化了)
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
// 进入辅助扩容
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
// 作为第一个扩容的线程
// 设置 sc = rs长度标识符的低16位左移至高16位 + 2
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
// 当CounterCell[]为null时,说明没有并发竞争,直接返回baseCount
// 当不为null时,说明存在并发,则统计CounterCell[] + baseCount的总数
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
}
jdk8 ConcurrentHashMap分析的更多相关文章
- java源码-ConcurrentHashMap分析-1
ConcurrentHashMap源码分析 版本jdk8 摈弃了jdk7之前的segement段锁: 首先分析一下put方法,大致的流程就是首先对key取hash函数 判断是否first节点是否存在 ...
- 基于JDK1.8的ConcurrentHashMap分析
之前看过ConcurrentHashMap的分析,感觉也了解的七七八八了.但昨晚接到了面试,让我把所知道的ConcurrentHashMap全部说出来. 然后我结结巴巴,然后应该毫无意外的话就G了,今 ...
- ConcurrentHashMap 分析
转载请注明出处:http://blog.csdn.net/crazy1235/article/details/76795383 构造函数 JDK 1.5 引入了 ConcurrentHashMap . ...
- ConcurrentHashMap分析
1.ConcurrentHashMap锁分段技术 ConcurrentHashMap使用锁分段技术,首先将数据分成一段一段地存储,然后给每一段数据配一把锁,当一 ...
- Java并发集合(三)-ConcurrentHashMap分析和使用
1 http://ifeve.com/hashmap-concurrenthashmap-%E7%9B%B8%E4%BF%A1%E7%9C%8B%E5%AE%8C%E8%BF%99%E7%AF%87% ...
- ConcurrentHashMap源码解析 JDK8
一.简介 上篇文章详细介绍了HashMap的源码及原理,本文趁热打铁继续分析ConcurrentHashMap的原理. 首先在看本文之前,希望对HashMap有一个详细的了解.不然看直接看Concur ...
- Java同步数据结构之ConcurrentHashMap
前言 这是Java并发包最后一个集合框架的数据结构,其复杂程度也较以往任何数据结构复杂的多,顾名思义ConcurrentHashMap是线程安全版本的HashMap,总所周知HashMap是非线程安全 ...
- Java 容器源码分析之Map-Set-List
HashMap 的实现原理 HashMap 概述 HashMap 是基于哈希表的 Map 接口的非同步实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.此类不保证映射的顺序 ...
- ConcurrentHashMap 的实现原理
概述 我们在之前的博文中了解到关于 HashMap 和 Hashtable 这两种集合.其中 HashMap 是非线程安全的,当我们只有一个线程在使用 HashMap 的时候,自然不会有问题,但如果涉 ...
随机推荐
- saltstack的配置配置
一.为不同的环境设置不同的文件目录 1.1 修改配置文件 /etc/salt/master [root@node1 salt]# vim /etc/salt/master file_roots: ba ...
- 多vps管理面板
iis7远程桌面连接工具,又叫做iis7远程桌面管理软件,是一款绿色小巧,功能实用的远程桌面管理工具,其界面简洁,操作便捷,能够同时远程操作多台服务器,并且多台服务器间可以自由切换,适用 ...
- 「USACO08JAN」电话线Telephone Lines 解题报告
题面 大意:在加权无向图上求出一条从 \(1\) 号结点到 \(N\) 号结点的路径,使路径上第 \(K + 1\) 大的边权尽量小. 思路: 由于我们只能直接求最短路,不能记录过程中的具体的边--那 ...
- 1088 三人行 (20分)C语言
子曰:"三人行,必有我师焉.择其善者而从之,其不善者而改之." 本题给定甲.乙.丙三个人的能力值关系为:甲的能力值确定是 2 位正整数:把甲的能力值的 2 个数字调换位置就是乙的能 ...
- 1089 狼人杀-简单版 (20 分)C语言
以下文字摘自<灵机一动·好玩的数学>:"狼人杀"游戏分为狼人.好人两大阵营.在一局"狼人杀"游戏中,1 号玩家说:"2 号是狼人" ...
- java基础之----分布式事务tcc
最近研究了一下分布式事务框架,ttc,总体感觉还可以,当然前提条件下是你要会使用这个框架.下面分层次讲,尽量让想学习的同学读了这篇文章能加以操作运用.我不想废话,直接上干货. 一.什么是tcc?干什么 ...
- mui选择器和软键盘冲突解决
只需要让此节点失焦即可: onfocus="this.blur();"
- Mybatis-plus 实体类继承关系 插入默认值
在实际开发中,会定义一些公共字段,而这些公共字段,一般都是在进行操作的时候由程序自动将默认值插入.而公共的字段一般会被封装到一个基础的实体类中,同时实体类中会实现相应的getter setter 方法 ...
- html 小游戏合集(1.0)
最近做了个小游戏合集,有点沙雕,毕竟是1.0,将就看看. <!DOCTYPE html> <html> <head> <meta charset=" ...
- mac-air上安装 rabbitmq 并简单使用
简介: brew 安装 rabbitmq,docker安装rabbitmq 安装官方php-amqp 扩展 简单使用样例(发送10次helloworld