Java-HashMap源码分析

JDK1.8之前的HashMap采用的是数组和链表结合使用,也就是链表散列。HashMap 使用 key 的 hashcode 经过扰动函数处理过后得到hash值,然后通过 (n-1)&hash 得到当前元素存放的数组索引(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突

扰动函数就是HashMap的hash方法,使用hash方法的目的就是防止一些实现性较差的hashCode方法,就是为了减少hash碰撞

// JDK1.7的hash方法
static int hash(int h) {
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
// JDK1.8的hash方法
static final int hash(Object key) {
    int h;
    // key.hashCode():返回散列值也就是hashcode
    // ^ :按位异或
    // >>>:无符号右移,忽略符号位,空位都以0补齐
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

减少hash碰撞的方法

1、开放寻址法
基本思想:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。

公式

Hi = (H(key) + di) MOD m, i=1,2,…,k(k<=m-1)

H(key)为hash函数,m为表长,di为增量序列。增量序列的取值方式不同,相应的再散列方式也不同

线性探测法:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表

di = 1,2,3,4,5,6,...,n

二次探测法:冲突发生时,在表的左右进行跳跃式探测,比较灵活。

di = 1^2, -1^2, 2^2, -2^2, ....., k^2, -k^2

伪随机探测法:应建立一个伪随机数发生器,并给定一个随机数做起点

di = 伪随机序列
缺点:

  • 容易产生数据堆积,不适用于大规模的数据存储
  • 插入操作可能会出现多次冲突
  • 删除冲突元素时,需要对后面的元素作处理,实现较复杂
  • 节点规模很大时会浪费很多空间

2、二次hash(rehash)

这种方法是同时构造多个不同的hash函数

Hi = RHi(key), i=1,2,…,k

当 i=1 时取得的地址冲突时,会继续计算 i=2 时的地址,直到找到不冲突的地址为止。

这种方法不易产生聚集,但增加了计算时间

3、链地址法

为每一个hash值建立一个单链表,当发生冲突时,将记录插入到链表中

优点:

  • 处理冲突简单,无堆积现象
  • 各链表上的结点空间是动态申请的,它更适合于造表前无法确定表长的情况
  • 拉链法构造的散列表中,删除结点的操作易于实现

4、建立公共溢出区

将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表

HashMap

JDK1.7采用拉链法设计HashMap

jdk1.7-HashMap.jpg

JDK1.8的HashMap在1.7的基础上增加了链表转红黑树,当某个桶中的记录过大的话(当前是TREEIFY_THRESHOLD= 8),HashMap会动态的使用一个专门的TreeMap实现来替换掉它。提高查询效率,时间复杂度由O(n)减少为O(logn)

jdk1.8Hashmap.png

HashMap线程不安全的原因

1、多线程同时put可能导致元素丢失

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node[] tab; Node p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0){
        // 初始化
        n = (tab = resize()).length;
    }
    // 多线程的情况下,如果扩容进行到一半,在这里添加到新table的数据会被扩容再次hash的数据覆盖
    // put成功但是get的时候是null
    // 多线程多个hash值相同的进到这里判null,只有最后一个key成功
    if ((p = tab[i = (n - 1) & hash]) == null){
        tab[i] = newNode(hash, key, value, null);
    } else {
        Node e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k)))){
            e = p;
        }else if (p instanceof TreeNode){
            e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
        } else {
            for (int binCount = 0; ; ++binCount) {
                // 这里也会导致数据丢失
                if ((e = p.next) == null) {
                    // 如果一个线程执行到这里挂起,并且另一个线程正在扩容,扩容完成之后这里执行会导致内存泄露,通过get方法无法获取到这个node
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) {
                        treeifyBin(tab, hash);
                    }
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))){
                    break;
                }
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null){
                e.value = value;
            }
            return oldValue;
        }
    }
    // 多线程导致计数错误
    ++modCount;
    if (++size > threshold)
        resize();
    return null;
}

jdk1.7的HashMap并发下扩容可能形成环状链表,导致get操作时,CPU空转

// HashMap#getNode
do {
    if (e.hash == hash &&
        ((k = e.key) == key || (key != null && key.equals(k))))
        return e;
} while ((e = e.next) != null);

形成环状链表之后,如果查询环状链表中不存在的值就会一直循环。

jdk1.7的HashMap因为会将新添加的节点放在头节点所以会导致死循环

JDK1.8的HashMap已经修复了JDK1.7中插入节点的死循环问题,但是存在红黑树的死循环问题(多线程下操作同一对象时,对象内部属性的不一致性导致的)

JDK1.8死循环参考:https://blog.csdn.net/gs_albb/article/details/88091808

public class HashMap8 {
    public static void main(String[] args) {
        HashMapThread hmt0 = new HashMapThread();
        HashMapThread hmt1 = new HashMapThread();
        HashMapThread hmt2 = new HashMapThread();
        HashMapThread hmt3 = new HashMapThread();
        HashMapThread hmt4 = new HashMapThread();
        HashMapThread hmt5 = new HashMapThread();
        HashMapThread hmt6 = new HashMapThread();
        HashMapThread hmt7 = new HashMapThread();
        HashMapThread hmt8 = new HashMapThread();
        HashMapThread hmt9 = new HashMapThread();
        HashMapThread hmt10 = new HashMapThread();
        HashMapThread hmt11 = new HashMapThread();
        HashMapThread hmt12 = new HashMapThread();
        hmt0.start();
        hmt1.start();
        hmt2.start();
        hmt3.start();
        hmt4.start();
        hmt5.start();
        hmt6.start();
        hmt7.start();
        hmt8.start();
        hmt9.start();
        hmt10.start();
        hmt11.start();
        hmt12.start();
    }

}

class HashMapThread extends Thread {
    private AtomicInteger ai = new AtomicInteger(0);
    private static Map map = new HashMap<>(1);

    @Override
    public void run() {
        while (ai.get() < 5000) {
            map.put(new Person(ai.get(), "name" + Thread.currentThread().getName()), ai.get());
            ai.incrementAndGet();
        }
        System.out.println(Thread.currentThread().getName() + "执行结束完" + ai.get());
    }
}

可能出现如下循环异常


java8HashMap的死循环.png

死循环的两个地方

// HashMap#putVal  -> putTreeVal
// thread-3去获取红黑树的root节点的时候,红黑树在并发下死循环了
final TreeNode root() {
    // 多线程死循环
    for (TreeNode r = this, p;;) {
        if ((p = r.parent) == null){
            return r;
        }
        r = p;
    }
}
// HashMap#putVal  -> treeifyBin
final void treeify(Node[] tab) {
    for (TreeNode x = this, next; x != null; x = next) {
        next = (TreeNode)x.next;
        x.left = x.right = null;
        if (root != null) {
            K k = x.key;
            int h = x.hash;
            Class kc = null;
            // 多线程死循环
            for (TreeNode p = root;;) {
                int dir, ph;
                K pk = p.key;
                if ((ph = p.hash) > h){
                    dir = -1;
                }else if (ph < h){
                    dir = 1;
                }else if ((kc == null &&
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0){
                     dir = tieBreakOrder(k, pk);
                }
                TreeNode xp = p;
                // 红黑树循环导致find死循环
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    x.parent = xp;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        }
    }
}

还有可能死循环的地方 java.util.HashMap.TreeNode#split 中的for循环,在resize 的时候死循环。

红黑树的死循环


java8HashMap的死循环线程状态.png

ConcurrentHashMap

ConcurrentHashMap是线程安全的HashMap,有HashTable具有以下的区别

1、底层数据结构不同

JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;

2、实现线程安全的方式

在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;

Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

两者的对比:https://www.cnblogs.com/chengxiao/p/6842045.html

JDK1.8的ConcurrentHashMap(TreeBin: 红黑二叉树节点 Node: 链表节点)


jdk1.8-ConcurrentHashMap.jpg

ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。

JDK1.8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(log(N)))

synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

对于JDK1.8的ConcurrentHashMap主要分析一下几个方法

  • putVal
  • transfer
  • size

putVal

final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    // 计算出散列值
    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) {
            // 初始化
            tab = initTable();
        } else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            // 通过CAS去设置Node 存在空位置
            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) {
            // 转发节点的hash标识
            tab = helpTransfer(tab, f);
        } else {
            V oldVal = null;
            // 把当前需要操作的node锁住
            synchronized (f) {
                // 再次检查是不是f
                if (tabAt(tab, i) == f) {
                    // 正常单线程的插入流程
                    if (fh >= 0) {
                        // 在链表中插入
                        binCount = 1;
                        for (Node e = f; ; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                // 重复key直接替换 put -> onlyIfAbsent=false
                                if (!onlyIfAbsent) {
                                    e.val = value;
                                }
                                break;
                            }
                            Node pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node(hash, key, value, null);
                                break;
                            }
                        }
                    } else if (f instanceof TreeBin) {
                        // 在红黑树中插入  hash == -2
                        Node p;
                        binCount = 2;
                        if ((p = ((TreeBin) f).putTreeVal(hash, key, value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent) {
                                p.val = value;
                            }
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD) {
                    // 检查是否需要转为红黑树
                    treeifyBin(tab, i);
                }
                // 这里return是因为集合的长度没有变化,可以直接return
                if (oldVal != null) {
                    return oldVal;
                }
                break;
            }
        }
    }
    // binCount
    // 0: 没有hash冲突
    // 插入链表的位置index: 插入链表
    // 2: 插入红黑树
    // 修改cell的大小
    addCount(1L, binCount);
    return null;
}

addCount

// 从 putVal 传入的参数是 1, binCount   size数据长度都是+1  x=1
private final void addCount(long x, int check) {
    CounterCell[] as;
    long b, s;
    // 如果counterCells不是空,那么会使用counterCells来计数
    // 如果counterCells空,在没有线程竞争的情况下使用baseCount voltile变量来计数
    // 如果CAS的时候存在线程竞争,并且竞争失败的线程回去初始化 counterCells
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a;
        long v;
        int m;
        boolean uncontended = true;
        // as == null 表示并未出现并发
        // 如果随机取余一个数组位置为空 或者 compareAndSwapLong(cellVlue) 失败  出现并发
        if (as == null || (m = as.length - 1) < 0 ||
            // 随机去一个值 & m
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            // as[0] 或者 as[1] == null
            (a = as[1 & m]) == null ||
            !(uncontended = U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            // 初始化 counterCells
            fullAddCount(x, uncontended);
            // 这里初始化肯定没有到达扩容范围,因此可以直接return
            return;
        }
        // 如果是单个node插入数组   check == 0
        // 怎么才能到这个if  =>  counterCells不是空  并且上面的if不成立
        // 直接跳过的可能性是: 一个线程正在初始化counterCells,并且里面都存在值
        // 但是又来了一个线程并且成功的设置了value = value + 1
        // 这时就会跑到这里来,如果这里不是链表和红黑树就可以直接返回了
        if (check <= 1) {
            return;
        }
        // 算出插入元素之后的size  遍历counterCells计算出总元素个数
        s = sumCount();
    }
    // 需要检查是否需要扩容
    if (check >= 0) {
        Node[] tab, nt;
        int n, sc;
        // 插入后的size >= sizeCtl(capacity)
        while (s >= (long) (sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            // 根据 length 得到一个标识
            // 如果n=16  前16位是 32  后16位 2^15  如果左移16位就是2^31 最小的负数
            // 如果第一个线程已经在开始扩容,下一个线程过来这里还是resizeStamp(16)
            int rs = resizeStamp(n);
            // sc如果小于0说明正在扩容
            if (sc < 0) {
                // 如果 sc 的低 16 位不等于 标识符(校验异常 sizeCtl 变化了)
                // 如果 sc == 标识符 << Shift + 1 (扩容结束了,不再有线程进行扩容)
                // (默认第一个线程设置 sc == rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs左移Shift + 1)
                // 如果 sc == 标识符 + 65535(帮助线程数已经达到最大)
                // 如果 nextTable == null(结束扩容了)
                // 如果 transferIndex <= 0 (转移状态变化了)
                // 结束循环
                // rs  =>  resizeStamp(tab.length)
                // sc = sizeCtl = (rs << RESIZE_STAMP_SHIFT) + 2
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == (rs << RESIZE_STAMP_SHIFT) + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0) {
                    break;
                }
                // 如果可以帮助扩容,那么将 sc 加 1. 表示多了一个线程在帮助扩容
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    // 扩容
                    transfer(tab, nt);
                }
                // sizeCtl = -2 表示有一个线程在进行扩容
            } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) {
                //  如果不在扩容,将 sc 更新:标识符左移 16 位 然后 + 2. 也就是变成一个负数。
                //  高 16 位是标识符,低 16 位初始是 2.
                // 开始扩容
                transfer(tab, null);
            }
            s = sumCount();
        }
    }
}

transfer

// 第一次扩容 nextTab = null
private final void transfer(Node[] tab, Node[] nextTab) {
    int n = tab.length, stride;
    // thinkPad++ 8核
    // 16 >>> 3 / cpu = 2/8 = 0
    if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) {
        // subdivide range
        stride = MIN_TRANSFER_STRIDE;
    }
    // 如果小于16  则取16 stride = 16
    if (nextTab == null) {            // initiating
        try {
            // << 1  扩大一倍
            @SuppressWarnings("unchecked")
            Node[] nt = (Node[]) new Node[n << 1];
            nextTab = nt;
        } catch (Throwable ex) {
            // try to cope with OOME
            // 这里就表示最大的容量可以是 MAX_VALUE = 2^31 - 1
            sizeCtl = Integer.MAX_VALUE;
            return;
        }
        nextTable = nextTab;
        // 扩容之前的长度 比如:16
        transferIndex = n;
    }
    // 新数组长度
    int nextn = nextTab.length;
    // 在新数组的头上增加一个需要转发的Node节点
    ForwardingNode fwd = new ForwardingNode(nextTab);
    boolean advance = true;
    boolean finishing = false; // to ensure sweep before committing nextTab
    for (int i = 0, bound = 0; ; ) {
        Node f;
        int fh;
        /**
          * 多线程在这里会被指派不同区段的扩容任务,比如当前size=32时,需要扩容
          * 线程A进行扩容  transferIndex = 16  i=31  bound=16  n=32  nextn=64  它负责的区间就是[16,31]
          * 线程B进行扩容  transferIndex = 0   i=15  bound=0   它负责的区间就是[0,15]
          * 因为存在 transferIndex 这volatile变量的限制,当没有任务给线程分配时,就会返回,将sizeCtl - 1
          */
        while (advance) {
            int nextIndex, nextBound;
            // 这里的--i很关键,下面每完成一个节点的搬运就会来这里将i指向前一位
            if (--i >= bound || finishing) {
                advance = false;
            } else if ((nextIndex = transferIndex) <= 0) {
                i = -1;
                advance = false;
            } else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,
                                           nextBound = (nextIndex > stride ? nextIndex - stride : 0))) {
                // 出现并发时这里只有一个线程可以成功 设置transferIndex的值为 nextBound  期望值是 nextIndex
                bound = nextBound;
                // transferIndex = 16    i=15
                i = nextIndex - 1;
                advance = false;
            }
        }
        /**
         * 如果当前的Map size=32
         *  线程A进行扩容  transferIndex = 16  i=31  bound=16  n=32  nextn=64
         *  线程B进行扩容  transferIndex = 0   i=15  bound=0
         *  线程再次来进行扩容的时候,会进入第一个 else if,直接就退出了
         *                transferIndex = 0   i=-1  bound=0
         */
        if (i < 0 || i >= n || i + n >= nextn) {
            int sc;
            if (finishing) {
                // 说明当前扩容已经完成,结束扩容逻辑并设置下一个扩容需要的阈值,这里并没有CAS去修改
                // 什么样的线程可以到这里
                // 1、进来扩容却没有拿到node节点 并且刚好在这里其他线程完成了扩容
                // 2、这个finishing变量是一个局部变量,线程私有,只能是当前线程去把它设置为true
                nextTable = null;
                table = nextTab;
                // 64 - 16 = 0.75*64 = 48 扩容阈值
                sizeCtl = (n << 1) - (n >>> 1);
                return;
            }
            // 走到这里说明当前线程的扩容工作已经完成,需要将扩容线程数减1
            if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT){
                    // 说明SC存在问题,当前的sizeCtl与table长度不对应
                    return;
                }
                finishing = advance = true;
                // recheck before commit
                i = n;
            }
            // i = 区间的最后一位
        } else if ((f = tabAt(tab, i)) == null) {
            // [0 ~ stride-1]  [stride,stride*2-1]
            // 如果区间的最后一位是null,则将扩容前的ForwardNode放入其中
            advance = casTabAt(tab, i, null, fwd);
        } else if ((fh = f.hash) == MOVED) {
            // already processed
            // 如果hash为MOVED,说明已经有线程在处理这一段扩容了
            // 重新获取扩容段
            advance = true;
        } else {
            // 把区间的最后一个节点锁住(最后一个节点在这里不为null),进行元素的移动
            synchronized (f) {
                // 再次检查
                if (tabAt(tab, i) == f) {
                    Node ln, hn;
                    // 最后一个节点不是红黑树,可能是链表,也可能是单个的元素  => 使用链表遍历
                    if (fh >= 0) {
                        // 之前计算hash值是和 &(n-1)  &n   =0的不需要变位置  =1的新索引需要+n
                        // newIndex = hash & 1 1111
                        // oldIndex = hash & 0 1111
                        // 头节点的runBit
                        int runBit = fh & n;
                        Node lastRun = f;
                        for (Node p = f.next; p != null; p = p.next) {
                            int b = p.hash & n;
                            if (b != runBit) {
                                runBit = b;
                                // 最后一个 b!=runBit 的节点p作为lastRun
                                lastRun = p;
                            }
                        }
                        if (runBit == 0) {
                            // 索引不变的链表
                            ln = lastRun;
                            hn = null;
                        } else {
                            // 索引+n的链表
                            hn = lastRun;
                            ln = null;
                        }
                        for (Node p = f; p != lastRun; p = p.next) {
                            int ph = p.hash;
                            K pk = p.key;
                            V pv = p.val;
                            // 构建两个新链表  新链表的顺序问题
                            if ((ph & n) == 0) {
                                // next = ln
                                // 新链表的顺序还是和原来的顺序一致
                                ln = new Node(ph, pk, pv, ln);
                            }else {
                                hn = new Node(ph, pk, pv, hn);
                            }
                        }
                        // ln链表放入新table的i位置
                        setTabAt(nextTab, i, ln);
                        // hn链表放入新table的i+n位置
                        setTabAt(nextTab, i + n, hn);
                        // 将需要转发的Node 放在区间的最后一个位置
                        setTabAt(tab, i, fwd);
                        // 再次进入循环
                        advance = true;
                    } else if (f instanceof TreeBin) {
                        // 区间的最后一个节点是红黑树,将红黑树移动到新table,将需要转发的Node 放在区间的最后一个位置
                        TreeBin t = (TreeBin) f;
                        TreeNode lo = null, loTail = null;
                        TreeNode hi = null, hiTail = null;
                        int lc = 0, hc = 0;
                        for (Node e = t.first; e != null; e = e.next) {
                            int h = e.hash;
                            TreeNode p = new TreeNode
                                (h, e.key, e.val, null, null);
                            if ((h & n) == 0) {
                                if ((p.prev = loTail) == null)
                                    lo = p;
                                else
                                    loTail.next = p;
                                loTail = p;
                                ++lc;
                            } else {
                                if ((p.prev = hiTail) == null)
                                    hi = p;
                                else
                                    hiTail.next = p;
                                hiTail = p;
                                ++hc;
                            }
                        }
                        ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                        (hc != 0) ? new TreeBin(lo) : t;
                        hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                        (lc != 0) ? new TreeBin(hi) : t;
                        setTabAt(nextTab, i, ln);
                        setTabAt(nextTab, i + n, hn);
                        setTabAt(tab, i, fwd);
                        advance = true;
                    }
                }
            }
        }
    }
}

fullAddCount

private final void fullAddCount(long x, boolean wasUncontended) {
    int h;

    if ((h = ThreadLocalRandom.getProbe()) == 0) {
        ThreadLocalRandom.localInit();      // force initialization
        // 多线程下获取随机数,ThreadLocalRandom更适合用在多线程下,能大幅减少多线程并行下的性能开销和资源争抢
        h = ThreadLocalRandom.getProbe();
        wasUncontended = true;
    }
    boolean collide = false;                // True if last slot nonempty
    for (; ; ) {
        CounterCell[] as;
        CounterCell a;
        int n;
        long v;
        if ((as = counterCells) != null && (n = as.length) > 0) {
            // 计数器不是null,并且已经初始化完成
            // 随机取出计数器中的cell
            if ((a = as[(n - 1) & h]) == null) {
                if (cellsBusy == 0) {            // Try to attach new Cell
                    CounterCell r = new CounterCell(x);
                    // Optimistic create
                    if (cellsBusy == 0 && U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                        // cellsBusy 设置为1  表示正在创建counterCell
                        boolean created = false;
                        try {
                            // Recheck under lock
                            CounterCell[] rs;
                            int m, j;
                            // 双重检测
                            if ((rs = counterCells) != null &&
                                (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            cellsBusy = 0;
                        }
                        if (created)
                            break;
                        // 双重检测失败,证明其他线程在counterCells同一个位置建立了CounterCell
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            } else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash
            // 再次尝试去修改value
            else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
                break;
            else if (counterCells != as || n >= NCPU)
                collide = false;            // At max size or stale
            else if (!collide)
                collide = true;
            // 对counterCells进行扩容
            else if (cellsBusy == 0 &&
                     U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
                try {
                    // cellsBusy置为1 表示正在创建cell
                    if (counterCells == as) {// Expand table unless stale
                        // 扩大为原来的两倍,最后continue
                        CounterCell[] rs = new CounterCell[n << 1];
                        for (int i = 0; i < n; ++i){
                            rs[i] = as[i];
                        }
                        counterCells = rs;
                    }
                } finally {
                    cellsBusy = 0;
                }
                collide = false;
                continue;                   // Retry with expanded table
            }
            h = ThreadLocalRandom.advanceProbe(h);
        } else if (cellsBusy == 0 && counterCells == as &&
                   // [A]
                   U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
            // 初始化counterCells
            // [B1]
            boolean init = false;
            try {
                // Initialize table
                if (counterCells == as) {
                    // 默认的长度时2
                    CounterCell[] rs = new CounterCell[2];
                    // 随机选一个 0 或者 1 来存储当前的cell
                    rs[h & 1] = new CounterCell(x);
                    counterCells = rs;
                     // [B2]
                    init = true;
                }
            } finally {
                cellsBusy = 0;
            }
            if (init)
                break;
        } else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
            // 最后如果counterCells没有初始化并且在初始化counterCells的时候修改cellsBusy失败,
            // 则会再次尝试去修改baseCount
            // Fall back on using base
            break;
    }
}
  • cellsBusy:1 表示 CounterCells 正在创建 ,0 表示其他状态。
  • baseCount:用于统计 Map 中的 k-v 数,baseCount 用于在没有出现竞争的情况下统计。
  • counterCells:数一个数组,每个数组项是一个 int,在 baseCount 上的递增出现竞争时会去取 counterCells 中的一个项进行递增,最终的 Map 中的 k-v 数总和是 baseCount 和 counterCells 所有计数之和,见下面的 subCount 方法。
  • 三个地方都会去 CAS set cellsBusy,从 0 改成 1,并发下只有一个线程能进入临界区代码。临界区代码用 try finally 去保证 cellsBusy 最终一定会被设置回 0,相当于解锁。
  • 如果 A 线程运行到「A」,另 B 线程运行到「B1」,如果 A 线程被挂起(比如 CPU 切换了执行线程),然而 B 线程继续执行,一直执行到了「B2」,这个时候 A 线程继续执行,如果没有 counterCells == as 判断,实惠重复创建 counterCells 的。这个是需要 counterCells 判断的理由。这个和并发下的单例设计模式一样,在进入锁以后需要重新判空一次。
  • 这里的计数是使用了 counterCells,从源码的注释看,这个实现思路是和 java.util.concurrent.atomic.LongAdder 一致的。
  • fullAddCount 方法的实现思路几乎和 LongAdder 一致。

remove

public V remove(Object key) {
    return replaceNode(key, null, null);
}
// replaceNode和putVal方法类似,
// 链表替换的核心代码
if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
    // 找到了需要移除key
    V ev = e.val;
    if (cv == null || cv == ev || (ev != null && cv.equals(ev))) {
        // 这里因为cv=null,所以后面都不需要判断
        oldVal = ev;
        // 修改前一个节点和后一个节点的指向
        if (value != null){
            e.val = value;
        }else if (pred != null){
            pred.next = e.next;
        }else{
            setTabAt(tab, i, e.next);
        }
    }
    break;
}
// 红黑树的替换逻辑
if ((r = t.root) != null && (p = r.findTreeNode(hash, key, null)) != null) {
    // 找到需要移除的节点P
    V pv = p.val;
    if (cv == null || cv == pv || (pv != null && cv.equals(pv))) {
        // 修改p的value = null
        oldVal = pv;
        if (value != null){
            p.val = value;
        // TreeBin#removeTreeNode
        }else if (t.removeTreeNode(p)){
            setTabAt(tab, i, untreeify(t.first));
        }
    }
}
// 修改size
addCount(-1L, -1);

clear

public void clear() {
    long delta = 0L; // negative number of deletions
    int i = 0;
    Node[] tab = table;
    // 循环数组中的节点
    while (tab != null && i < tab.length) {
        int fh;
        Node f = tabAt(tab, i);
        if (f == null){
            ++i;
        }else if ((fh = f.hash) == MOVED) {
            // 节点状态是moved 说明正在扩展,则将当前线程也去参与扩容
            // 扩容完成之后在clear
            tab = helpTransfer(tab, f);
            i = 0; // restart
        } else {
            // 锁住当前的node  防止put remove 扩容等操作
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    // hash值大于0说明是链表,返回头节点f
                    // 小于0 则返回红黑树的 first 节点
                    Node p = (fh >= 0 ? f :
                                    (f instanceof TreeBin) ? ((TreeBin) f).first : null);
                    // 统计链表或者树的个数
                    while (p != null) {
                        --delta;
                        p = p.next;
                    }
                    // 把数组中当前的node 置为null
                    setTabAt(tab, i++, null);
                }
            }
        }
    }
    if (delta != 0L){
        // 修改size
        addCount(delta, -1);
    }
}

你可能感兴趣的:(Java-HashMap源码分析)