关于JDK1.8下的ConcurrentHashMap的put方法和get方法源码,以及红黑树的插入详解

关于JDK1.8下的ConcurrentHashMap

一、ConcurrentHashMap的创建

ConcurrentHashMap的创建是通过New的方式生成的。

ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();

实际是一个无参构造。

public ConcurrentHashMap() {
}

二、ConcurrentHashMap的put()方法

ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
concurrentHashMap.put("key","value");

这里的实际调用为putVal(key, value, false),所以我们只关注putVal()方法即可。

public V put(K key, V value) {
        return putVal(key, value, false);
    }

下面我们分析下putVal(key, value, false)

  1. 首先第一步便是判断keyvalue是否存在为空的情况,如果存在直接抛出空指针异常(NullPointerException)快速失败。
  2. 获取keyhashCode,通过keyhashCode右移16位后的值与原keyhashCode进行运算,并且和HASH_BITS = 0x7fffffff进行运算得到最终的hashCode值。这样做的目的是为了减少hash碰撞。
  3. for (Node<K,V>[] tab = table;;)Node数组进行循环,此处相当于while (true)Node<K,V> f为当前索引位置的数据、n为数组长度、i为数据要存储的索引位置。
  4. 判断tab是否为空,以及tab大小是否为0,首次进入为null,执行初始化initTable()方法。首先先判断tab是否为null或者tab长度是否为0,然后判断sizeCtl是否<0<0表示正在有线程进行初始化。Thread.yield();线程调度器该线程让出CPU,这里我们的sizeCtl0,表示还没有进行初始化。通过CAS修改sizeCtlU.compareAndSwapInt(this, SIZECTL, sc, -1)状态为-1,表示正在进行初始化。CAS修改完成后返回true。再次判断tab是否为空,以及tab大小是否为0,这里为双重保障。为了防止其他线程以及完成了初始化。然后进行判断int n = (sc > 0) ? sc : DEFAULT_CAPACITY;,此处sc0,返回默认值16(DEFAULT_CAPACITY)。创建Node数组进行长度初始化,初始化长度为16。最后计算sc = n - (n >>> 2);扩容阈值。在finally中进行赋值扩容阈值。break;退出循环,返回初始化好的Node数组。
  5. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) 去判断当前索引下标在tab数组中是否为空,为空则通过CAS向对应tab中的i位置增加一个NodeNode里面包含了keyHashCode以及keyvalue。增加成功后返回。
  6. else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f);如果f.hash == MOVED说明该数组正在有线程进行扩容,需要帮忙进行扩容。helpTransfer(tab, f)
  7. 如果没有其他线程在进行扩容的话,使用synchronized锁住当前Node节点,if (tabAt(tab, i) == f) 如果当前i索引位置下的Node等于此次操作的Nodeif (fh >= 0)判断当前是否是链表。binCount = 1;初始化链表遍历头部,从1开始遍历。进行for循环,如果Node节点存储的HashCode和当前要存入的keyhashCode相同,并且两个Key在经过==或者equals发现两者有一个相等。那么进行value值得覆盖。此处因注意onlyIfAbsent,只有当onlyIfAbsentfalse才进行覆盖。如果不相等则判断当前Node节点的下个节点是否为空,为空则重新创建Node节点,并将该Node节点存放在当前Node节点的Next上。如果当前Node的Next不为空,那么继续循环直到,找到Next为空的Node节点,然后进行处理。
  8. 如果不是链表代表是红黑树,走红黑树处理逻辑。
  9. 通过binCount判断是否需要进行扩容。
  private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        //  首先先判断`tab`是否为`nul`l或者`tab`长度是否为`0`
        while ((tab = table) == null || tab.length == 0) {
        //  然后判断`sizeCtl`是否`<0`,`<0`表示正在有线程进行初始化。`Thread.yield();`线程调度器该线程让出`CPU`,这里我们的`sizeCtl`为`0`,表示还没有进行初始化。
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
                //  通过`CAS`修改`sizeCtlU.compareAndSwapInt(this, SIZECTL, sc, -1)`状态为`-1`,表示正在进行初始化。`CAS`修改完成后返回`true`。
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                // 再次判断`tab`是否为空,以及`tab`大小是否为`0`,这里为双重保障。为了防止其他线程以及完成了初始化。
                    if ((tab = table) == null || tab.length == 0) {
                    // 然后进行判断`int n = (sc > 0) ? sc : DEFAULT_CAPACITY;`,此处`sc`为`0`,返回默认值`16(DEFAULT_CAPACITY)`。
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        // 最后计算`sc = n - (n >>> 2);`扩容阈值
                        sc = n - (n >>> 2);
                    }
                } finally {
                // 在`finally`中进行赋值扩容阈值。
                    sizeCtl = sc;
                }
                // 退出循环
                break;
            }
        }
        //返回初始化好的`Node`数组。
        return tab;
    }
final V putVal(K key, V value, boolean onlyIfAbsent) {
// 1. 首先第一步便是判断`key`和`value`是否存在为空的情况,如果存在直接抛出空指针异常(`NullPointerException`)快速失败。
        if (key == null || value == null) throw new NullPointerException();
// 2. 获取`key`的`hashCode`,通过`key`的`hashCode`右移`16`位后的值与原`key`的`hashCode`进行`或`运算,
//并且和`HASH_BITS = 0x7fffffff`进行`与`运算得到最终的`hashCode`值。这样做的目的是为了减少`hash`碰撞。
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            

// 判断`tab`是否为空,以及`tab`大小是否为`0`,首次进入为`null`,执行初始化`initTable()`方法。
//首先先判断`tab`是否为`nul`l或者`tab`长度是否为`0`,然后判断`sizeCtl`是否`<0`,`<0`表示正在有线程进行初始化。`Thread.yield();`线程调度器该线程让出`CPU`,这里我们的`sizeCtl`为`0`,表示还没有进行初始化。
//通过`CAS`修改`sizeCtlU.compareAndSwapInt(this, SIZECTL, sc,-1)`状态为`-1`,表示正在进行初始化。
//`CAS`修改完成后返回`true`。再次判断`tab`是否为空,以及`tab`大小是否为`0`,这里为双重保障。为了防止其他线程以及完成了初始化。
//然后进行判断`int n = (sc > 0) ? sc : DEFAULT_CAPACITY;`,此处`sc`为`0`,返回默认值`16(DEFAULT_CAPACITY)`。
//创建`Node`数组进行长度初始化,初始化长度为`16`。最后计算`sc
= n - (n >>> 2);`扩容阈值。在`finally`中进行赋值扩容阈值。`break;`退出循环,返回初始化好的`Node`数组。

            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
                // `else if ((f = tabAt(tab, i = (n - 1) & hash)) == null)` 去判断当前索引下标在`tab`数组中是否为空,
                //为空则通过`CAS`向对应`tab`中的`i`位置增加一个`Node`	,`Node`里面包含了`key`的`HashCode`以及`key`和`value`。增加成功后返回。
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // 如果f.hash == MOVED说明该数组正在有线程进行扩容,需要帮忙进行扩容。
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                // 如果没有其他线程在进行扩容的话,使用`synchronized`锁住当前`Node`节点
                synchronized (f) {
                //`if (tabAt(tab, i) == f)` 如果当前`i`索引位置下的`Node`等于此次操作的`Node`
                    if (tabAt(tab, i) == f) {
                    // `if (fh >= 0)`判断当前是否是链表。
                        if (fh >= 0) {
                        //`binCount = 1;`初始化链表遍历头部,从`1`开始遍历
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //进行`for`循环,如果`Node`节点存储的`HashCode`和当前要存入的`key`的`hashCode`相同,
                                //并且两个`Key`在经过`==`或者`equals`发现两者有一个相等
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    // 此处因注意`onlyIfAbsent`,只有当`onlyIfAbsent`为`false`才进行覆盖。
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                // 如果不相等则判断当前`Node`节点的下个节点是否为空,为空则重新创建`Node`节点,
                                //并将该`Node`节点存放在当前`Node`节点的`Next`上。如果当前Node的Next不为空,那么继续循环直到,找到Next为空的Node节点,然后进行处理。
                                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) {
                    if (binCount >= TREEIFY_THRESHOLD)
                    // 扩容以及转换红黑树处理,这里首次转换红黑树根节点为黑。
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

三、ConcurrentHashMap的红黑树插入putTreeVal方法

  1. 这里红黑树插入使用到了TreeNode,如果为空需要进行初始化,红黑树转换时第一次需要初始化TreeNode阶段,初始化调用有参构造将HashCodeKeyValue进行存储到TreeNode中,然后跳出循环。
  2. 当前红黑树节点的的hash值和当前key的哈希值做大小判断,大于则dir置为-1dir是为了进行左插入和右插入判断,小于则dir置为1dir是为了进行左插入和右插入判断。
  3. 如果当前节点的key==或者equals要存入的key直接返回当前节点。
  4. 如果Class为空并且,并且comparableClassFor == null comparableClassFor作用是判断key是否实现了自定义排序接口Comparable,如果没有实现就返回空,如果实现了就判断是否是String类型,如果是String就直接返回String.Class。如果不是就通过class.getGenericInterfaces获取当前key的类实现的接口。然后遍历实现的接口,判断接口是否为ParameterizedType并且,ParameterizedTypeRawType是否为Comparable接口,并且泛型类型不为空返回当前keyClass对象。
  5. compareComparables如果没有实现Comparable接口就不走排序,否则走Comparable排序compareTo通过value进行排序。
  6. 首次进来为false触发下面if处理,进行初始化。判断当前节点得左节点是否为空,并且获取当前节点得左节点判断是否当前keyhashCode是否小于当前节点的左节点hashcode,或者小于右节点,如果存在key重复的现象则返回对应TreeNode节点。
  7. 不存在key重复的现象走tieBreakOrder,判断当前节点的key和需要保存的keyhashcode相同的情况下通过排序进行比较。相等则返回0
  8. dir小于等于0则获取当前节点的左分支,否则获取右分支。当分支为空时新建TreeNode节点,根据dir来选择插入右分支还是左分支。
  9. 小于等于0插入左分支,大于0插入右分支。判断上级节点是否是否为红节点,首次红黑树创建red为黑节点为false。这里如果为黑节点那么对应子节点为红节点。如果为红节点需要进行加锁操作 。通过cas进行加锁操作,这里锁住的是当前ConcurrentHashMap对象。红黑树插入节点后,需要重新平衡balanceInsertion返回重新平衡后的根节点。
final TreeNode<K,V> putTreeVal(int h, K k, V v) {
            Class<?> kc = null;
            boolean searched = false;
            for (TreeNode<K,V> p = root;;) {
                int dir, ph; K pk;
                //这里红黑树插入使用到了`TreeNode`,如果为空需要进行初始化,
                //红黑树转换时第一次需要初始化`TreeNode`阶段,
                //初始化调用有参构造将`HashCode`和`Key`和`Value`进行存储到`TreeNode`中,然后跳出循环。
                if (p == null) {
                    first = root = new TreeNode<K,V>(h, k, v, null, null);
                    break;
                }
                // 当前红黑树节点的的hash值和当前key的哈希值做大小判断
                else if ((ph = p.hash) > h)
                // 大于则dir置为-1,dir是为了进行左插入和右插入判断
                    dir = -1;
                else if (ph < h)
                // 小于则dir置为1,dir是为了进行左插入和右插入判断
                    dir = 1;
                // 如果当前节点的key,==或者equals要存入的key直接返回当前节点。    
                else if ((pk = p.key) == k || (pk != null && k.equals(pk)))
                    return p;
                // 如果Class为空并且,并且comparableClassFor == null,
                else if ((kc == null &&
                //comparableClassFor作用是判断key是否实现了自定义排序接口Comparable,
                //如果没有实现就返回空,如果实现了就判断是否是String类型,
                //如果是String就直接返回String.Class。
                //如果不是就通过class.getGenericInterfaces获取当前key的类实现的接口。
                //然后遍历实现的接口,判断接口是否为ParameterizedType并且,
                //ParameterizedType的RawType是否为Comparable接口,
                //并且泛型类型不为空返回当前key的Class对象。
                          (kc = comparableClassFor(k)) == null) ||
                          //compareComparables如果没有实现Comparable接口就不走排序,
                          //否则走Comparable排序compareTo通过value进行排序。
                         (dir = compareComparables(kc, k, pk)) == 0) {
                         // 首次进来为false触发下面if处理,进行初始化。
                    if (!searched) {
                        TreeNode<K,V> q, ch;
                        searched = true;
                        // 判断当前节点得左节点是否为空,并且获取当前节点得左节点判断是否当前key得hashCode是否小于当前节点的左节点hashcode,或者小于右节点,如果存在key重复的现象则返回对应TreeNode节点
                        if (((ch = p.left) != null &&
                             (q = ch.findTreeNode(h, k, kc)) != null) ||
                             // 判断当前节点得右节点是否为空,并且
                            ((ch = p.right) != null &&
                             (q = ch.findTreeNode(h, k, kc)) != null))
                            return q;
                    }
                    // 不存在key重复的现象走这里,判断当前节点的key和需要保存的key在hashcode相同的情况下通过排序进行比较。相等则返回0,
                    dir = tieBreakOrder(k, pk);
                }	
                // dir小于等于0则获取当前节点的左分支,否则获取右分支。当分支为空时新建TreeNode节点,根据dir来选择插入右分支还是左分支。
                TreeNode<K,V> xp = p;
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    TreeNode<K,V> x, f = first;
                    first = x = new TreeNode<K,V>(h, k, v, f, xp);
                    if (f != null)
                        f.prev = x;
                    // 小于等于0插入左分支    
                    if (dir <= 0)
                        xp.left = x;
                    else
                    // 大于0插入右分支
                        xp.right = x;
                    // 判断上级节点是否是否为红节点,首次红黑树创建red为黑节点为false,
                    // 这里如果为黑节点那么对应子节点为红节点。
                    if (!xp.red)
                        x.red = true;
                    // 如果为红节点需要进行加锁操作    
                    else {
                    // 通过cas进行加锁操作,这里锁住的是当前ConcurrentHashMap对象
                        lockRoot();
                        try {
                        // 红黑树插入节点后,需要重新平衡
                            root = balanceInsertion(root, x);
                        } finally {
                        // 释放锁
                            unlockRoot();
                        }
                    }
                    break;
                }
            }
            // 检查红黑树的平衡性
            assert checkInvariants(root);
            return null;
        }

四、ConcurrentHashMap的get()方法

  1. Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek; tab为Node数组、e为需要获取到的Node节点、nNode数组长度、ehKeyhashCodeekNodekey
  2. int h = spread(key.hashCode());获取keyhashCode,通过keyhashCode右移16位后的值与原keyhashCode进行运算,并且和HASH_BITS = 0x7fffffff进行运算得到最终的hashCode值。这样做的目的是为了减少hash碰撞。
  3. 判断Node数组是否为Null,或者Node数组长度是否大于0,为Null或者小于0,表示Node未被初始化或者Node数组没有数据。并且根据数组长度-1keyhashCode进行运算,去Node数组中寻找是否存在该数据。不存在直接返回Null
  4. 如果获取出来对应的Node节点的HashCode和当前出来KeyHashCode一致,并且Node节点中的Key和当前查询的Key==或者equals判断一致那么直接返回Node中存储的Value值。
  5. 如果不相等则判断当前Node中存储的HashCode是否为-1-1表示为红黑树代表,需要进入红黑树查询。红黑树查找也为while查找,通过遍历Node节点的Next节点进行Hash==equals判断。
  6. 如果经过Node数组头节点和红黑树都没有查找到,那么就需要进行Node链表的遍历了,如果Node节点的Next不为空进行While循环,通过HashCode==equals进行判断,判断成立返回对应的Node节点的Value值。
    public V get(Object key) {
    //`Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;` 
    //`tab`为Node数组、`e`为需要获取到的`Node`节点、
    //`n`为`Node`数组长度、`eh`为`Key`的`hashCode`,`ek`为`Node`的`key`。
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //` int h = spread(key.hashCode());`获取`key`的`hashCode`,
        //通过`key`的`hashCode`右移`16`位后的值与原`key`的`hashCode`进行`或`运算,
        //并且和`HASH_BITS = 0x7fffffff`进行`与`运算得到最终的`hashCode`值。
        //这样做的目的是为了减少`hash`碰撞。
        int h = spread(key.hashCode());
        //判断`Node`数组是否为`Null`,或者`Node`数组长度是否大于`0`,
        //为`Null`或者小于`0`,表示`Node`未被初始化或者`Node`数组没有数据。
        //并且根据数组长度`-1`和`key`的`hashCode`进行`与`运算,去`Node`数组中寻找是否存在该数据。
        //不存在直接返回`Null`。
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            // 如果获取出来对应的Node节点的HashCode和当前出来Key的HashCode一致,
            //并且Node节点中的Key和当前查询的Key,==或者equals判断一致那么直接返回Node中存储的Value值。 
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            // 如果不相等则判断当前Node中存储的HashCode是否为-1,-1表示为红黑树代表,需要进入红黑树查询。
            // 红黑树查找也为while查找,通过遍历Node节点的Next节点进行Hash、==、equals判断。
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            // 如果经过Node数组头节点和红黑树都没有查找到,那么就需要进行Node链表的遍历了,
            //如果Node节点的Next不为空进行While循环,通过HashCode和==、equals进行判断,
            //判断成立返回对应的Node节点的Value值。
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

由于时间原因扩容还未分析,请耐心等待。感谢观看。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值