上一篇是分享的是《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 LO<strong style="color:transparent">来源gaodai#ma#com搞@@代~&码网</strong>AD_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源码解读一》的分享。
- 也欢送大家交换探讨,该文章若有不正确的中央,心愿大家多多包涵。
- 你们的反对就是我最大的能源,如果对大家有帮忙给个赞哦~~~