文章目录四、扩容2、帮助扩容3、链表转红黑树触发扩容 五、get操作遇到正在扩容六、更新节点七、总结
一、前言
对的基本概念有一个初步印象后,接下来才是真正的探索 。
扩容是重头戏,看过的人都说难 。确实,和java7版本比起来,难度真不是一个量级的 。有些细节看着莫名其妙,一想就是好几天,看似想明白也只能算是猜想合理,直呼Doug Lea的心思是真的细啊!
深究细节是费时且痛苦的,欣喜的是,怎么也想不明白的逻辑,发现这次是源码错了!官方JDK!现在市面上普遍都在用java8,怎么可能存在bug呢?
首先思考几个问题:
二、put添加元素
老规矩,从put操作切入 。添加元素的过程中可能会触发初始化数组,触发链表与红黑树转换,触发扩容机制等,有意思的是,一个简单的元素计数,作者都花了大心思 。
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 =http://www.kingceram.com/post/= null) throw new NullPointerException();// 1. 哈希值高低位扰动int hash = spread(key.hashCode());int binCount = 0;for (Node[] tab = table;;) {Node f; int n, i, fh;if (tab == null || (n = tab.length) == 0)// 2. tab 为空 初始化tab = initTable();else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {// 3. tab不为null,则通过(n - 1) & hash 计算 tab对应索引下标,找到node// node为null说明没有发生hash冲突,cas 设置新节点node到tab的对应位置,成功则结束循环if (casTabAt(tab, i, null,new Node(hash, key, value, null)))break;// no lock when adding to empty bin}else if ((fh = f.hash) == MOVED)// 4. 发现哈希值为MOVED时,// 说明数组正在扩容,帮助扩容,这个节点只可能是ForwardingNodetab = helpTransfer(tab, f);else {// 5.正常情况下发生哈希冲突V oldVal = null;synchronized (f) {// 再次检查i位置的节点是否还是f// 如果有变动则重新循环if (tabAt(tab, i) == f) {if (fh >= 0) {// 6. fh>=0 是链表binCount = 1;for (Node e = f;; ++binCount) {K ek;if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {// 链表中已经有hash相等且(key地址相等 or key值相等)// 则判断是否需要替换// put onlyIfAbsent=false,新值替换旧值// putIfAbsent onlyIfAbsent=true,新值不替换旧值oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}// 解决hash冲突的方式// 链表法,新节点放在了链表尾部(尾插法),这里和jdk1.7不一样Node pred = e;if ((e = e.next) == null) {pred.next = new Node(hash, key,value, null);break;}}}else if (f instanceof TreeBin) {// 7.红黑树Node p;binCount = 2;if ((p = ((TreeBin)f).putTreeVal(hash, key,value)) != null) {// putTreeVal的返回值是已经存在的节点// p != null 说明 key已经存在,看是否需要替换valueoldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}if (binCount != 0) {// 8. binCount,链表的长度>=8时 可能变为红黑树,也可能是扩容// 数组长度小于64时,是扩容数组if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)// 若旧值不为null,则说明是替换,不需要后面的addCountreturn oldVal;break;}}}// 9. 元素数量+1addCount(1L, binCount);return null;}
(1)首先计算key的哈希值,并做高低位扰动 。
(2)数组table若为空,则初始化() 。初始化的工作量很小,就是实例化一个数组,但是如何在多线程环境安全初始化呢?这里就涉及到的值变化:
private final Node[] initTable() {Node[] tab; int sc;while ((tab = table) == null || tab.length == 0) {if ((sc = sizeCtl) < 0)Thread.yield(); // lost initialization race; just spi// SIZECTL 设置为 -1,相当于轻量级自旋锁else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {// 如果某个线程成功地把sizeCtl 设置为-1,它就拥有了初始化的权利// 等到初始化完成,再把sizeCtl设置成当前容量的3/4,即为扩容阈值try {if ((tab = table) == null || tab.length == 0) {int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")Node