源码-ConcurrentHashMap(jdk1.8)
·
基本属性
常量
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final int DEFAULT_CAPACITY = 16;
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
private static final float LOAD_FACTOR = 0.75f;
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
private static final int MIN_TRANSFER_STRIDE = 16;
private static int RESIZE_STAMP_BITS = 16;
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.
*/
hash值是-1,表示这是一个forwardNode节点
static final int MOVED = -1; // hash for forwarding nodes
hash值是-2 表示这时一个TreeBin节点
static final int TREEBIN = -2; // hash for roots of trees
static final int RESERVED = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
static final int NCPU = Runtime.getRuntime().availableProcessors();
成员变量
// Node元素数组,大小是2的整数次幂
transient volatile Node<K,V>[] table;
private transient volatile Node<K,V>[] nextTable;
private transient volatile long baseCount;
//hash表初始化或扩容时的一个控制位标识
//负数代表正在进行初始化或扩容操作
//-1代表正在初始化, N 表示有N-1个线程正在进行扩容操作
//正数或0代表hash表还没有被初始化,这个数值表示初始化或下一次进行扩容的大小
private transient volatile int sizeCtl;
private transient volatile int transferIndex;
private transient volatile int cellsBusy;
private transient volatile CounterCell[] counterCells;
private transient KeySetView<K,V> keySet;
private transient ValuesView<K,V> values;
private transient EntrySetView<K,V> entrySet;
与HashMap一样,concurrentHashMap内部存储结构也是Node<K,V>,存储结构相似,但有一些差别
- val 和 next 属性设置了volatile
- 不允许调用setValue方法直接改变Node的value
- 增加了find方法辅助map.get()方法
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;
}
...
}
当链表长度过长的时候,会转换为TreeNode。但是与HashMap不相同的是,它并不是直接转换为红黑树,而是把这些结点包装成TreeNode放在TreeBin对象中,由TreeBin完成对红黑树的包装。而且TreeNode在ConcurrentHashMap集成自Node类,而并非HashMap中的集成自LinkedHashMap.Entry<K,V>类,也就是说TreeNode带有next指针,这样做的目的是方便基于TreeBin的访问。
put 方法
final V putVal(K key, V value, boolean onlyIfAbsent) {
//不允许存放 null
if (key == null || value == null) throw new NullPointerException();
// hash散列算法:使高16位参与运算,并保证hash值为正数
int hash = spread(key.hashCode());
int binCount = 0;
// 死循环
for (Node<K,V>[] tab = table;;) {
// f: 当前key对应的桶元素
// n: 当前table的大小
// i: 当前key 应该放在table的位置索引
// fh:当前key对应桶元素的hash值
Node<K,V> f; int n, i, fh;
// 如果table没有初始化
if (tab == null || (n = tab.length) == 0)
// 初始化hash表,也是懒加载
tab = initTable();
// 通过路由算法找到当前值,应该存放的位置i
// 如果当前位置没有值
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//通过cas 将当前位置由null 变为 node对象
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 如果当前位置有元素,且hash值为 -1
else if ((fh = f.hash) == MOVED)
// 帮助扩容
tab = helpTransfer(tab, f);
// 出现hash冲突,需要将数据挂在链表上,或红黑树中
else {
// 老的val
V oldVal = null;
// 锁当前桶中已存在的对象 f,只锁了自己的桶
synchronized (f) {
// 拿到i索引位置的数据 和 f是不是同一个
if (tabAt(tab, i) == f) {
// 当前桶下不是树,可能是链表,或者是空
if (fh >= 0) {
// 计数,记录链表长度
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
// 判断二者的值是否是一样
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
// 值一样,说明不是hash冲突,而是同一个value
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
// 追加到链表后面
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
// 当前f已经树化
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) {
// 链表长度超过树阈值,树化
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
是否可以存null
在单线程环境中,不会存在一个线程操作该 HashMap 时,其他的线程将该 HashMap 修改的情况,可以通过 contains(key)来做判断是否存在这个键值对,从而做相应的处理;
而在多线程环境下,可能会存在多个线程同时修改键值对的情况,这时是无法通过contains(key)来判断键值对是否存在的,这会带来一个二义性的问题(无法判断是值本身为null还是说集合中就没这个值),Doug Lea说二义性是多线程中不能容忍的!
initTable 方法
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
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;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
treeifyBin方法
private final void treeifyBin(Node<K,V>[] tab, int index) {
//b:
//n: 当前table数组长度
//sc:
Node<K,V> b; int n, sc;
if (tab != null) {
// 如果table数组长度小于 64,尝试扩容
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
// 尝试扩容
tryPresize(n << 1);
// 转红黑树
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode<K,V> hd = null, tl = null;
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));
}
}
}
}
}
tryPresize方法
private final void tryPresize(int size) {
//c: 计算扩容数组长度
int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
tableSizeFor(size + (size >>> 1) + 1);
//sc: sizeCtl
int sc;
while ((sc = sizeCtl) >= 0) {
// sc =0 代表还没有初始化,sc > 0 代表下次扩容的大小
Node<K,V>[] tab = table; int n;
// 没有初始化
if (tab == null || (n = tab.length) == 0) {
// 取二者的最大值
n = (sc > c) ? sc : c;
// 通过cas 设置sizeCtl为-1, 标识正在初始化
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 防止ABA的问题
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) {
// 获取扩容戳(高16位做扩容标识,低16位做扩容线程数(2代表1个线程在扩容))
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);
}
// 暂时没有线程扩容,我先设置sizeCtl标记,设置为2,代表一个线程扩容
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
}
}
}
更多推荐

所有评论(0)