ConcurrentHashMap详解

Java 同时被 3 个专栏收录
107 篇文章 4 订阅
119 篇文章 14 订阅

books 基本特点

  • 和HashMap功能基本一致,主要是为了解决HashMap线程不安全问题;
  • java7中的基本设计理念就是切分成多个Segment块,默认是16个,也就是说并发度是16,可以初始化时显式指定,后期不能修改,每个Segment里面可以近似看成一个HashMap,每个Segment块都有自己独立的ReentrantLock锁,所以并发操作时每个Segment互不影响;java8中将Segment块换成了Node,每个Node有自己的锁,即每个Node都有自己的并发度;
  • 不允许空值和空键,否则会抛出异常;

books 核心方法put()、get()解读

结合源码来看put()动作做了哪些事情(源码取自jdk1.8):

  • key-value的检验,不允许为空,否则抛出异常;
  • 用while(true)无限重试put动作,直到put成功;
  • 如果数组没有初始化过则先进行初始化工作;
  • 如果数组中该hashcode对应位置不存在元素,则将该元素以CAS方式放入,对应的casTabAt()方法;
  • 如果数组中存在元素,用synchronized块保证同步操作,循环判断链表中是否存在该value,存在则返回oldValue,不存在则插入链表末端,put成功跳出循环;
  • 如果是红黑树,则将元素放入红黑树节点中;
  • put完成后根据链表长度是否大于8且数组长度是否大于64来判断是否将链表转换成红黑树;
  • 返回oldValue值;
    public V put(K var1, V var2) {
        return this.putVal(var1, var2, false);
    }

    final V putVal(K var1, V var2, boolean var3) {
        if (var1 != null && var2 != null) {
            int var4 = spread(var1.hashCode());
            int var5 = 0;
            ConcurrentHashMap.Node[] var6 = this.table;

            while(true) {
                int var8;
                while(var6 == null || (var8 = var6.length) == 0) {
                    var6 = this.initTable();
                }

                ConcurrentHashMap.Node var7;
                int var9;
                if ((var7 = tabAt(var6, var9 = var8 - 1 & var4)) == null) {
                    if (casTabAt(var6, var9, (ConcurrentHashMap.Node)null, new ConcurrentHashMap.Node(var4, var1, var2, (ConcurrentHashMap.Node)null))) {
                        break;
                    }
                } else {
                    int var10 = var7.hash;
                    if (var7.hash == -1) {
                        var6 = this.helpTransfer(var6, var7);
                    } else {
                        Object var11 = null;
                        synchronized(var7) {
                            if (tabAt(var6, var9) == var7) {
                                if (var10 < 0) {
                                    if (var7 instanceof ConcurrentHashMap.TreeBin) {
                                        var5 = 2;
                                        ConcurrentHashMap.TreeNode var18;
                                        if ((var18 = ((ConcurrentHashMap.TreeBin)var7).putTreeVal(var4, var1, var2)) != null) {
                                            var11 = var18.val;
                                            if (!var3) {
                                                var18.val = var2;
                                            }
                                        }
                                    }
                                } else {
                                    var5 = 1;
                                    ConcurrentHashMap.Node var13 = var7;

                                    while(true) {
                                        if (var13.hash == var4) {
                                            Object var14 = var13.key;
                                            if (var13.key == var1 || var14 != null && var1.equals(var14)) {
                                                var11 = var13.val;
                                                if (!var3) {
                                                    var13.val = var2;
                                                }
                                                break;
                                            }
                                        }

                                        ConcurrentHashMap.Node var15 = var13;
                                        if ((var13 = var13.next) == null) {
                                            var15.next = new ConcurrentHashMap.Node(var4, var1, var2, (ConcurrentHashMap.Node)null);
                                            break;
                                        }

                                        ++var5;
                                    }
                                }
                            }
                        }

                        if (var5 != 0) {
                            if (var5 >= 8) {
                                this.treeifyBin(var6, var9);
                            }

                            if (var11 != null) {
                                return var11;
                            }
                            break;
                        }
                    }
                }
            }

            this.addCount(1L, var5);
            return null;
        } else {
            throw new NullPointerException();
        }
    }

再看get()动作做了哪些事情:

  • 判断数组是否为空,为空直接返回null;
  • 根据hash值查找,如果hash值相等且key也相等,则直接返回value值;
  • 如果key的hash值小于0,说明是红黑树,则进入红黑树查找到该节点值;
  • 否则进入循环链表读取该value值并返回;
    public V get(Object var1) {
        int var8 = spread(var1.hashCode());
        ConcurrentHashMap.Node[] var2 = this.table;
        ConcurrentHashMap.Node var3;
        int var5;
        if (this.table != null && (var5 = var2.length) > 0 && (var3 = tabAt(var2, var5 - 1 & var8)) != null) {
            int var6 = var3.hash;
            Object var7;
            if (var3.hash == var8) {
                var7 = var3.key;
                if (var3.key == var1 || var7 != null && var1.equals(var7)) {
                    return var3.val;
                }
            } else if (var6 < 0) {
                ConcurrentHashMap.Node var4;
                return (var4 = var3.find(var8, var1)) != null ? var4.val : null;
            }
            while((var3 = var3.next) != null) {
                if (var3.hash == var8) {
                    var7 = var3.key;
                    if (var3.key == var1 || var7 != null && var1.equals(var7)) {
                        return var3.val;
                    }
                }
            }
        }
        return null;
    }

books jdk1.7升级到1.8,ConcurrentHashMap的变化

  • 锁方面:由分段锁(Segment继承自ReentrantLock)升级为CAS+synchronized实现;
  • 数据结构层面:将Segment变为了Node,每个Node独立,原来默认的并发度16,变成了每个Node都独立,提高了并发度;
  • hash冲突:1.7中发生hash冲突采用链表存储,1.8中先使用链表存储,后面满足条件后会转换为红黑树来优化查询;
  • 查询复杂度:1.7中链表查询复杂度为O(N),1.8中红黑树优化为O(logN));

books 链表长度为什么超过8要转为红黑树?

  • 默认的是链表结构,并不是一开始就是红黑树结构,因为链表比红黑数占用的空间较少;
  • hash冲突导致链表长度真正达到8的概率极小,约为一千万分之一,同时也考虑到红黑树查询比链表快;
  • 14
    点赞
  • 2
    评论
  • 86
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页

打赏

饭一碗

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值