CHM
put方法
1.检查key和value不为null
2.计算hash值
int hash = spread(key.hashCode()); // key.hashCode() = h ,(h ^ (h >>> 16)) & HASH_BITS
3.自旋插入
int binCount = 0; Node<K, V>[] tab = table //将全局table赋值tab
4.初始化table
if (tab == null || (n = tab.length) == 0) tab = 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 //放弃时间片,释放cpu //若没有线程正在进行初始化,则使用CAS操作将sizeCtl设置为-1,表示当前线程正在进行初始化 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if ((tab = table) == null || tab.length == 0) { //DEFAULT_CAPACITY 默认容量16 int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n]; //创建一个容量为16的数组 table = tab = nt; //奖数组的地址赋值给table sc = n - (n >>> 2); //sc =12 ,当数组的容量达到12后会进行扩容 } } finally { sizeCtl = sc; //当数组的容量达到12后会进行扩容 } break; } } return tab; }
5.初始化完成就进行真正的插入操作
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//若tab[i]==null,则代表当前位置还没有值,则不使用锁而是直接进行CAS操作 if (casTabAt(tab, i, null, new Node<K, V>(hash, key, value, null))) break; //操作成功则结束自旋,否则进行下一次循环 }
6.当i位置已经存在元素时,需要进行尾插,返回历史value,没有历史值则返回null
else { V oldVal = null; synchronized (f) { //f = tabAt(tab, i = (n - 1) & hash)) f 为Tab[i] 加锁同步 if (tabAt(tab, i) == f) { // tab[i]的位置还没有改变 if (fh >= 0) { //节点正常,代表还是链表结构 binCount = 1; //初始链表的长度为 1 tab[i] for (Node<K, V> e = f;; ++binCount) { //从头结点开始遍历 K ek; //int hash = spread(key.hashCode()); //是同一个节点或者key相同 if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value;//如果不是只在缺少的情况下put则会进行value的更新 break; } Node<K, V> pred = e; //开始向后遍历 if ((e = e.next) == null) { //没有下一个节点,则直接链接到尾部 pred.next = new Node<K, V>(hash, key, value, null); break; } //e = e.next 在向后遍历的过程中检查有没有key相同的节点,若存在相同的key的节点则替换value值,否则遍历到最后的位置进行尾部插入 } } else if (f instanceof TreeBin) { //当前的bucket已经转换为红黑树 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 && tab.length>=64 ,转换成红黑树,否则进行扩容,--》扩容后16->32 treeifyBin(tab, i); if (oldVal != null) return oldVal;//当oldVal!=null时代表修改,所以直接返回,不需要增加个数 break; } }
7.增加节点记数
-
CAS操作baseCount+1
-
CAS操作分片计数数组a的value+1
-
进入fullAddCount(x, uncontended)计数
-
counterCells数组==null,则初始化一个大小为2的数组,并且根据随机数h&1得到一个元素位置,该元素value=1
-
如果存在线程在操作cell,直接CAS操作baseCount+=1
-
countCells已经初始化,元素下标 i=(n - 1) & h,如果该位置未初始化,则进行初始化该元素value=1
-
位置i已经初始化,CAS操作i位置的元素value+1
-
addCount(1L, binCount); //binCount 当前链表的长度 需要判断加上当前元素后需不需要扩容或者树的转换 //1.新增tab[i] ---> binCount = 0 //2.存在一个节点 ---> binCount = 1 //3.存在两个节点 ---> binCount = 2
private final void addCount(long x, int check) { //binCount 当前链表长度 CounterCell[] as; //分片计数 long b, s; if ((as = counterCells) != null ||