如果不使用自定义参数创建一个ConcurrentHashMap,调用默认方法
那么会在put元素时,创建一个大小为16(默认大小)的ConcurrentHashMap。
put方法:
put方法直接调用putVal方法。
我们首先需要知道几个变量的含义:
sizeCtl
-1:代表正在初始化。
负数但不是-1:正在扩容。
未初始化以前,保存table的初始化大小或者0。
初始化以后,保存扩容阈值。
MOVED
static final int MOVED = -1; // hash for forwarding nodes
MOVED,int常量,值为-1。
putVal
final V putVal(K key, V value, boolean onlyIfAbsent) { #首先,判断key和value是否为空,concurrentHashMap不允许null的key或者value。 if (key == null || value == null) throw new NullPointerException(); #计算hash值。下文有讲spread方法。 int hash = spread(key.hashCode()); #这个用来保存table数组在index(根据key计算出的下标)处的元素个数。 int binCount = 0; for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; K fk; V fv; #然后我们判断table是否为空,如果为空(创建后第一次put数据),我们就会调用initTable方法进行初始化。 #下文中有介绍initTable方法。 if (tab == null || (n = tab.length) == 0) tab = initTable(); #如果数组不为空,那么我们计算下标(n-1)& hash ,然后判断数组在该位置是否有值。 #如果为空,那么使用key和value创建node对象,然后使用CAS将对象存放在这个位置。 #如果成功了,就跳出循环。否则我们需要继续循环。 #下一次,很有可能下标处头节点已经不为空,如果为空,就继续进行这一步骤。 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value))) break; // no lock when adding to empty bin } #判断头结点的hash值是否为MOVED。 #如果为MOVED,说明concurrenthashmap正在扩容。那么我们调用helpTransfer方法来帮助扩容。 #同时将tab赋值为扩容后的table。所以,如果插入元素时,concurrenthashmap正在扩容,并且该位置的元素已经 #被转移到了新的concurrenthashmap,那么线程会先帮助concurrenthashmap进行扩容,扩容后再进行put操作。 #我们下面会讲到helpTransfer方法。 else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); #不讲onlyIfAbsent,基本不会用到。 else if (onlyIfAbsent // check first node without acquiring lock && fh == hash && ((fk = f.key) == key || (fk != null && key.equals(fk))) && (fv = f.val) != null) return fv; #如果已经初始化过了,同时也不在扩容状态,那么我们就开始做更新/插入操作。 #因为扩容是从后往前,逐步扩容,有可能concurrenthashmap正在扩容,但是还没有扩容到index处,那么我们就知道 #如果扩容时,发现index处正在做插入操作,那么扩容会阻塞,等待插入完成。 else { V oldVal = null; #这里用synchronized获取f(头节点)的锁。 synchronized (f) { #获取后再判断一次table[index]处是否还是之前的头对象。 #如果不是,继续循环(binCount为0,不会跳出循环。看if块下面的代码), #否则,开始更新或者添加元素。如果头结点的hash>=0,那么table[index]处存放的是一个链表,我们只需要遍历这个链表就可以了。 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); 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; } } else if (f instanceof ReservationNode) throw new IllegalStateException("Recursive update"); } } #binCount代表index处的元素数量,!=0说明我们put成功。 if (binCount != 0) { #扩容完成后,我们根据节点数进行判断,如果大于等于树化阈值,那么转为红黑树。那么是不是>=8就会转成红黑树呢? #答案是不一定,我们需要看treeifyBin方法,后面有讲这个方法。 if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); #注意,这里如果过oldVal不为空,说明我们做的是一次更新操作,那么就直接返回,不需要调用addCount方法 #addCount方法用于维护map中元素的总数 if (oldVal != null) return oldVal; break; } } } #如果是一次插入操作,那么可能要对map的node总数加1,然后可能要进行扩容,具体逻辑在addCount方法中,我们下面会讲到。 addCount(1L, binCount); return null; }
spread这个方法
非常简单,将hash