结构介绍
1.8中concurrentHashMap采用数组+链表+红黑树的方式存储,并且采用CAS+SYN的方式。在1.7中主要采用的是数组+链表,segment分段锁+reentrantlock。本篇主要在1.8基础上介绍下.
那么,我们的主要重点是分析什么呢,其实主要就是put 的整体流程。数组如何初始化,添加到数组、链表、红黑树、以及对应的扩容机制。
//添加数据
public V put(K key, V value) {
return putVal(key, value, false);
}
散列函数
put方法实际上调用的是putVal()方法。
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 1.判断为空 直接返回空指针
if (key == null || value == null) throw new NullPointerException();// key和value不允许null
int hash = spread(key.hashCode());//两次hash,减少hash冲突,可以均匀分布
}
这里为什么要对 h >>> 16 ,然后在进行 异或运算 & 操作。
其实主要还是为了让hash高16位也参与到索引位置的计算中。减少hash冲突。
static final int spread(int h) {
return (h ^ (h >>> 16)) & HASH_BITS;
}
我们假设h 位 :00011000 00000110 00111000 00001100
00011000 00000110 00111000 00001100 h
^
00000000 00000000 00011000 00000110 h >>> 16
00011000 00000110 00111000 00001100
&
00000000 00000000 00000111 11111111 2048 - 1
ConcurrentHashMap是如何根据hash值,计算存储的位置?
(数组长度 - 1) & (h ^ (h >>> 16))
00011000 00000110 00110000 00001100 key1-hash
00011000 00000110 00111000 00001100 key2-hash
&
00000000 00000000 00000111 11111111 2048 - 1
Node中的hash值除了可能是数据的hash值,也可能是负数。
// static final int MOVED = -1; // 代表当前位置数据在扩容,并且数据已经迁移到了新数组
// static final int TREEBIN = -2; // 代表当前索引位置下,是一个红黑树。 转红黑树,TreeBin有参构造
// static final int RESERVED = -3; // 代表当前索引位置已经被占了,但是值还没放进去呢。 compute方法
初始化数组
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();// key和value不允许null
int hash = spread(key.hashCode());//两次hash,减少hash冲突,可以均匀分布
int binCount = 0;//i处结点标志,0: 未加入新结点, 2: Tre