共计 4449 个字符,预计需要花费 12 分钟才能阅读完成。
上一篇是分享的是《Scala – 类的定义》,这篇给大家分享《ConcurrentHashMap 源码》。明天先更新一部分,一会儿要出门,体谅。
ConcurrentHashMap 源码解读一
首先就先来说一下几个全局变量
private static final int MAXIMUM_CAPACITY = 1 << 30; // 最大容量 2 的 30 次方
private static final int DEFAULT_CAPACITY = 16; // 默认容量 1<<4
private static final float LOAD_FACTOR = 0.75f; // 负载因子
static final int TREEIFY_THRESHOLD = 8; // 链表转为红黑树,大于 8 小于 6 先对链表数组进行翻倍扩容操作
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;//stamp 高位标识挪动位数
private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
static final int MOVED = -1; // forwarding nodes 的 hash 值,如果 hash 值等于 - 1 代表线程帮助扩容
static final int TREEBIN = -2; // roots of trees 的 hash 值,如果 hash 等于 - 2 代表,以后桶是红黑树
static final int RESERVED = -3; // transient reservations 的 hash 值
// usable bits of normal node hash,在 hash 计算的时候使用到,与 HashMap 计算出来的 hash 值进行与操作
static final int HASH_BITS = 0x7fffffff;
static final int NCPU = Runtime.getRuntime().availableProcessors(); // 可用处理器数量
而后是几个全局属性
transient volatile Node<K,V>[] table;// 以后 ConcurrentHashmap 的 Node 数组,正在应用的数组
private transient volatile Node<K,V>[] nextTable;//ForwardNode 所指向的下一个表,正在扩容的数组(还未应用)private transient volatile long baseCount;// 如果应用 CAS 计数胜利,应用该值进行累加,计数用的
// 扩容设置的参数,默认为 0,当值 =- 1 的时候,代表以后有线程正在进行扩容操作
// 当值等于 - n 的时候,代表有 n 个线程一起扩容,其中 n - 1 线程是帮助扩容
// 当在初始化的时候指定了大小,这会将这个大小保留在 sizeCtl 中,大小为数组的 0.75
private transient volatile int sizeCtl;// 标记状态以及数组阈值
private transient volatile int transferIndex;// 数组扩容的时候用到
private transient volatile int cellsBusy;
// 如果应用 CAS 计算失败,也就是说以后处于高并发的状况下,那么
// 就会应用 CounterCell[] 数组进行计数,相似 jdk1.7 分段锁的模式,锁住一个 segment
// 最初 size() 办法统计进去的大小是 baseCount 和 counterCells 数组的总和
private transient volatile CounterCell[] counterCells;// 计数数组。
首先是有参结构,这里如果是
ConcurrentHashMap chm = new ConcurrentHashMap(15);
那么其实容量不是 15,而是 32;
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 这个办法的,15+7+1=23;
private static final int tableSizeFor(int c) {
int n = c - 1;//23-1=22 0b10110
n |= n >>> 1;// 10110 | 01011 = 11111, 上面都是 11111 也就是 31
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;//31+1 = 32
}
所以这就是最初的容量,为 32,也就是有参的参数的两倍最近的 2 的次方数。
接下来就将 put 办法
final V putVal(K key, V value, boolean onlyIfAbsent) {//onlyIfAbsent 跟 hashmap 一样,就是判断是否要笼罩,默认为 false,笼罩。if (key == null || value == null) throw new NullPointerException();//// 这句话能够看出,ConcurrentHashMap 中不容许存在空值,这个是跟 HashMap 的区别之一 // 通过这个机制,咱们能够通过 get 办法获取一个 key,如果抛出异样,阐明这个 key 不存在
int hash = spread(key.hashCode());// 这个办法就相当于基于计算 hash 值。int binCount = 0;// 这个是记录这个桶的元素个数,目标是用它来判断是否须要转换红黑树,for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 状况一:如果数组为空或者长度为 0,进行初始化工作
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 状况二:如果获取的地位的节点为空,阐明是首节点插入状况,也就是该桶地位没有元素,利用 cas 将元素增加。else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,//cas 加自旋(和外侧的 for 形成自旋循环),保障元素增加平安
new Node<K,V>(hash, key, value, null)))// 如果加胜利了,那么就 break,否则再通过 for 的死循环进行判断
break; // no lock when adding to empty bin
}
// 状况三:如果 hash 计算失去的桶的地位元素的 hash 值为 moved,也就是 -1,证实正在扩容,那么就帮助扩容。else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
//hash 计算的桶地位元素不为空,且以后没有处于扩容操作,进行元素增加
// 状况四:这个桶有元素,则执行插入操作,有两种可能,一是这个桶对应的链表没有雷同的 key,那么久再链表尾插入 node 节点,而是有雷同的 key,那么久替换其 value。else {
V oldVal = null;
synchronized (f) { // 对以后桶进行加锁,保障线程平安,执行元素增加操作 // 将桶地位的元素锁住,那么在该桶位中的链表或者红黑树进行增加元素的话,就是平安的,只有这个线程拿住了这个锁
if (tabAt(tab, i) == f) {// 因为增加元素之后可能链表曾经变成红黑树了,那么这个 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)))) {// 进行节点判断,如果都一样,那么笼罩旧值。oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {// 尾插法,如果 e 的下一个不是 null,那么循环会让 pred 变成 e,直到最初节点,此时 e 的下一个为 null 的话
// 那么也就是 pred 下一个为 null,那么插入到 pred 下一个即可。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) {if (binCount >= TREEIFY_THRESHOLD)// 链表长度大于 / 等于 8,有可能将链表转成红黑树,因为在 treeifyBin(tab, i); 办法中还有一个判断数组长度是否小于 64 的判断,如果小于 64,就不会
// 树化。只是数组扩容。treeifyBin(tab, i);
if (oldVal != null) // 如果是反复键,间接将旧值返回
return oldVal;
break;
}
}
}
addCount(1L, binCount);// 增加的是新元素,保护汇合长度,并判断是否要进行扩容操作
return null;
}
总结:并发 map,jdk1.8 的状况下,底层跟 hashmap 一样,也是数组加链表加红黑树。
- 以上就是《ConcurrentHashMap 源码解读一》的分享。
- 也欢送大家交换探讨,该文章若有不正确的中央,心愿大家多多包涵。
- 你们的反对就是我最大的能源,如果对大家有帮忙给个赞哦~~~
正文完