ConcurrentHashMap源码剖析 (4)

jdk8的ConcurrentHashMap的数组初始化是在第一次添加元素时完成

//没有维护任何变量的操作,如果调用该方法,数组长度默认是16 public ConcurrentHashMap() { } //传递进来一个初始容量,ConcurrentHashMap会基于这个值计算一个比这个值大的2的幂次方数作为初始容量 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; }

注意:调用这个方法,得到的初始容量和我们之前讲的HashMap以及jdk7的ConcurrentHashMap不同,即使你传递的是一个2的幂次方数,该方法计算出来的初始容量依然是比这个值大的2的幂次方数

//调用四个参数的构造 public ConcurrentHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, 1); } //计算一个大于或者等于给定的容量值,该值是2的幂次方数作为初始容量 public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException(); if (initialCapacity < concurrencyLevel) // Use at least as many bins initialCapacity = concurrencyLevel; // as estimated threads long size = (long)(1.0 + (long)initialCapacity / loadFactor); int cap = (size >= (long)MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)size); this.sizeCtl = cap; } //基于一个Map集合,构建一个ConcurrentHashMap //初始容量为16 public ConcurrentHashMap(Map<? extends K, ? extends V> m) { this.sizeCtl = DEFAULT_CAPACITY; putAll(m); }

sizeCtl含义解释

注意:以上这些构造方法中,都涉及到一个变量sizeCtl,这个变量是一个非常重要的变量,而且具有非常丰富的含义,它的值不同,对应的含义也不一样,这里我们先对这个变量不同的值的含义做一下说明,后续源码分析过程中,进一步解释

sizeCtl为0,代表数组未初始化, 且数组的初始容量为16

sizeCtl为正数,如果数组未初始化,那么其记录的是数组的初始容量,如果数组已经初始化,那么其记录的是数组的扩容阈值

sizeCtl为-1,表示数组正在进行初始化

sizeCtl小于0,并且不是-1,表示数组正在扩容, -(1+n),表示此时有n个线程正在共同完成数组的扩容操作

2.2 jdk1.8添加安全 public V put(K key, V value) { return putVal(key, value, false); } final V putVal(K key, V value, boolean onlyIfAbsent) { //如果有空值或者空键,直接抛异常 if (key == null || value == null) throw new NullPointerException(); //基于key计算hash值,并进行一定的扰动 int hash = spread(key.hashCode()); //记录某个桶上元素的个数,如果超过8个,会转成红黑树 int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; //如果数组还未初始化,先对数组进行初始化 if (tab == null || (n = tab.length) == 0) tab = initTable(); //如果hash计算得到的桶位置没有元素,利用cas将元素添加 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //cas+自旋(和外侧的for构成自旋循环),保证元素添加安全 if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } //如果hash计算得到的桶位置元素的hash值为MOVED,证明正在扩容,那么协助扩容 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else { //hash计算的桶位置元素不为空,且当前没有处于扩容操作,进行元素添加 V oldVal = null; //对当前桶进行加锁,保证线程安全,执行元素添加操作 synchronized (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)))) { 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; } } } //树节点,将元素添加到红黑树中 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) { //链表长度大于/等于8,将链表转成红黑树 if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); //如果是重复键,直接将旧值返回 if (oldVal != null) return oldVal; break; } } } //添加的是新元素,维护集合长度,并判断是否要进行扩容操作 addCount(1L, binCount); return null; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsdsy.html