ConcurrentHashMap
类图
内部存储结构图
解决 hash冲突
- 拉链法
构造函数
这个可以指定初始化大小的构造函数,首先会判断提供的初始化值是否 大于 , 如果大于的话, 就直接初始化为最大值, 否则 就使用 tableSizeFor方法把提供的初始化值修改为 2 的次幂。
- MAXIMUM_CAPACITY static final int MAXIMUM_CAPACITY = 1 << 30
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;
}
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1))
对于给定的值, 把它调整为 2 的次幂.
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;
}
添加元素
public V put(K key, V value) {
return putVal(key, value, false);
}
spread
- final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
先计算出 key值的hash值, 然后开始遍历整个 table(整个 concurrentmap )
- transient volatile Node<K,V>[] table;
- 内部存储用的数据结构
- int MOVED = -1; // hash for forwarding nodes
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 空指针异常
if (key == null || value == null) throw new NullPointerException();
// 计算出 key的hash值
int hash = spread(key.hashCode());
int binCount = 0;
// 开始遍历整个 map
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 如果此时map还没元素, 就对map进行初始化操作
if (tab == null || (n = tab.length) == 0)
// 初始化整个map
tab = initTable();
// 使用CAS操作得到这个位置上面的值, 该 hash 值对应的数组下标,得到第一个节点 f
// 如果这个位置上面是空的, 在无锁状态下添加该值! 同样也是使用的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
}
// 如果当前节点的hash值是 MOVED, 则表明要开始扩容了
else if ((fh = f.hash) == MOVED)
// 开始走扩容的代码
tab = helpTransfer(tab, f);
else {
// 到这个 else里面的话, f 是一个非空值, 当前这个槽的位置是有数据的
V oldVal = null;
// 锁住这个 hash 位置的第一个元素 f (这个hash位置可能就一个元素, 也可以是一个链表)
synchronized (f) {
// 此时 i 的值 i = (n - 1) & hash
// 再做一次判断, 确保没有其他线程进来
if (tabAt(tab, i) == f) {
// 头节点的hash值大于等于0, 链表的形式
// fh = f.hash
if (fh >= 0) {
binCount = 1;
// 遍历整个链表
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 如果这个存在 key 已经存在了
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
// 看一看是否要覆盖它
if (!onlyIfAbsent)
e.val = value;
// 返回, 注意此时还在遍历链表的for中
// 这个遍历过程是个无限的循环
break;
}
// 驱动遍历的链表的代码
Node<K,V> pred = e;
// 找到链表的尾部, 新建立一个节点然后直接返回。
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//注意, 这个是 else if ()
//如果这个hash位置存放的是一个树
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
// 使用红黑树的方式添加一个新的节点
// 添加之后会返回一个 p
// 如果 p 不是空的话, 则表明在红黑树上面已经存在了这个
// key 并且 p就是原来的节点
// 然后开始走是否覆盖的代码
if (
(
p =((TreeBin<K,V>)f).putTreeVal(hash, key,value)
)
!= null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// 注意,代码走到这里的简图如下
// 走到这里的时候,代表着同步代码块已经走完了
// 也就意味着元素已经添加了
// 此时检查一下是否有条件把链表转化为红黑树
// binCount 代表的就是数组当前hash位置存放了多少了节点
// 如果大于8个 就开始调用转化为红黑树
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
tabAt
使用Unsafe的本地方法得到这个位置的元素
- U
- sun.misc.Unsafe U;
static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
}
Node<K,V>[] initTable()
初始化 hash表
- sizeCtl
- transient volatile int sizeCtl;
- DEFAULT_CAPACITY
- 默认的容量
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// 当map为空的时候, 才开始后续的操作
while ((tab = table) == null || tab.length == 0) {
// 其他的线程得到了锁
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// cas操作把SIZECTL设置为 -1
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
// 设置为默认值
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
// 如果是默认值的话, 这个计算结果是 12
sc = n - (n >>> 2);
}
} finally {
// 改变sizeCtl 为 sc
sizeCtl = sc;
}
break;
}
}
return tab;
}
链表转化为红黑树
调用它的地方 得到转化的条件如下
- 当前hash位置的节点数量大于 8
- hash表的长度大于64
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
具体的转化步骤
- MIN_TREEIFY_CAPACITY = 64
/**
* Replaces all linked nodes in bin at given index unless table is
* too small, in which case resizes instead.
*/
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
// 这个表不为空
if (tab != null) {
// 表的长度小于64
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
// 走扩容机制
tryPresize(n << 1);
// 表的长度大于64
// 按照下面的图, 可以了解到index 就是头节点
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
// 锁住头节点
synchronized (b) {
// 双重检测
if (tabAt(tab, index) == b) {
TreeNode<K,V> hd = null, tl = null;
// 开始遍历链表建立红黑树的操作了
// b 是链表的头节点
for (Node<K,V> e = b; e != null; e = e.next) {
// 新建立一个树节点
TreeNode<K,V> p =
new TreeNode<K,V>(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
// 将红黑树设置到数组相应位置中
setTabAt(tab, index, new TreeBin<K,V>(hd));
}
}
}
}
}
扩容机制
调用它的地方 在调用 的 时候就已经把 容量翻倍
- 扩容大小: 一倍
private final void treeifyBin(Node<K,V>[] tab, int index) {
Node<K,V> b; int n, sc;
if (tab != null) {
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
}
扩容机制
/**
* 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) {
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
// 把新的容量调节为 2 的 n 次方。
int sc;
while ((sc = sizeCtl) >= 0) {
Node<K,V>[] tab = table; int n;
if (tab == null || (n = tab.length) == 0) {
n = (sc > c) ? sc : c;
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if (table == tab) {
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
}
}
else if (c <= sc || n >= MAXIMUM_CAPACITY)
break;
else if (tab == table) {
int rs = resizeStamp(n);
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;
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);
}
}
}