java架构师课程学习心得-第5篇-ConcurrentHashMap源码学习心得

ConcurrentHashMap源码学习心得

本文主要是对ConcurrentHashMap的构造函数,初始化方法,put流程中的转红黑树,数据扩容,数据迁移等方法的执行流程方面,做一个学习心得的分享,还有一些总结性的东西



ConcurrentHashMap的特点

  1. ConcurrentHashMap是数组+链表构成的,JDK1.8之后,加入了红黑树.
  2. ConcurrentHashMap默认数组初始化大小为16,如果瞎设置数字,它会自动调整成2的倍数.
  3. ConcurrentHashMap链表在长度为8之后,会自动转换成红黑树,
  4. 在数组正在扩容时,原来的红黑树的节点会被分成两组.每组节点的数量小于6就会在扩容后变成链表,否则变成红黑树
  5. ConcurrentHashMap扩容变化因子是0.75,也就是数组的3/4被占用之后,开始扩容。
  6. 在第一次调用PUT方法之前,ConcurrentHashMap是没有数组也没有链表的,在每次put元素之后,开始检查(生成)数组和链表

ConcurrentHashMap的源码分析

2 构造函数

2.1 无参构造函数,啥也没干
2.2 有参构造函数ConcurrentHashMap(int initialCapacity)
2.2.1 设置map容量大小,最大2的30次方,按照传入的参数计算一个最接近的2的次方数,公式:传入size大小的1.5倍,再向上取最近的2的N次方
2.2.2 设置扩容阈值,这里被设置为容量的大小,后面put流程中会修改为容量的3/4

public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }

3 put流程

3.1 调用putVal(key,value,false)
3.2 计算key的hash,如果map是空的就调用initTable(),详见4 初始化流程

3.3 map不为空,通过tabAt(tab,index)获取对应索引的元素f,这里会得到第一个节点元素,index= i = (n - 1) & hash
3.3.1 如果该元素为null,则表示该位置没有元素,于是使用casTabAt()函数,通过cas操作将数据放进去,成功这break跳出循环,结束put操作

3.4 获取元素f的hash值,赋给变量fhS
3.4.1 若fh=MOVED,既-1,表示该map正在扩容,则调用helpTransfer(tab, f)帮助数据迁移,参与一起扩容,
helpTransfer函数中调用transfer(tab, nextTab)进行扩容, 详见7 数据迁移流程

3.5 如果前面的条件都不满足,这里就是数组已经有元素了,这时候就该挂链表或者挂树了
3.6 先加锁,再获取头节点,并判断元素f是不是头节点
3.6.1 元素f是头节点,且fh>=0,表示该节点下面是链表,遍历链表,有相同的key就更新,没有就把数据插入到链表末尾,同时对节点数量进行记录binCount
3.6.2 如果f instanceof TreeBin,是树节点,则插入节点((TreeBin<K,V>)f).putTreeVal()

3.7 如果链表节点数量binCount>=8,则调用转树的函数treeifyBin(tab, i),详见5 转树函数流程

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

    /** Implementation for put and putIfAbsent */
    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<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            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
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                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;
    }

4 初始化流程

4.1 判断是否有其他线程再操作初始化if ((sc = sizeCtl) < 0),小于0,也就是sizeCtl=-1时,表示有线程在操作,则结束Thread.yield()
4.2 没有其他线程操作,则通过CAS把sizeCtl修改为-1,
4.3 然后就新建数组Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n],
4.4 计算扩容阈值sc=n - (n >>> 2),最后赋给sizeCtl=sc

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
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {
                    if ((tab = table) == null || tab.length == 0) {
                        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);
                    }
                } finally {
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

5 转树函数流程

treeifyBin(tab, i)
5.1 CurrentHashMap做了优化,这里如果数组长度小于64,它会先调用tryPresize(n << 1)扩容,详见7 扩容流程,扩容代表什么含义?-- 原来的链表会被1分为2 分别散落在不同的节点上
5.2 遍历链表,生成一棵红黑树,构建红黑树的过程就是将新建的TreeNode对象像链表一样连接起来,然后经过标记,旋转操作后,达到树的平衡
5.3 setTabAt(tab, index, new TreeBin<K,V>(hd)), 把数据放到红黑树中

    private final void treeifyBin(Node<K,V>[] tab, int index) {
        Node<K,V> b; int n, sc;
        if (tab != null) {
            if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
                tryPresize(n << 1);
            else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
                synchronized (b) {
                    if (tabAt(tab, index) == b) {
                        TreeNode<K,V> hd = null, tl = null;
                        for (Node<K,V> e = b; e != null; e = e.next) {
                            TreeNode<K,V> p =
                                new TreeNode<K,V>(e.hash, e.key, e.val,
                                                  null, null);
                            if ((p.prev = tl) == null)
                                hd = p;
                            else
                                tl.next = p;
                            tl = p;
                        }
                        setTabAt(tab, index, new TreeBin<K,V>(hd));
                    }
                }
            }
        }
    }

6 扩容流程

tryPresize(int size)
6.1 如果原来的map是空,就进行初始化
6.2 如果数组已经到达最大长度了,就直接结束
6.3 if (tab == table),应该是判断原来的map没有改变
6.3.1 if(sc<0) 表示这是其他扩容线程,Node<K,V>[] nt;nt = nextTable;获取新table,然后CAS调用transfer调用transfer(tab, nt)进行迁移
6.3.2 sc>=0,表示是第一次扩容,也就是第一个进行扩容的线程,CAS调用transfer(tab, null)进行迁移

    private final void tryPresize(int size) {
        int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY :
            tableSizeFor(size + (size >>> 1) + 1);
        int sc;
        while ((sc = sizeCtl) >= 0) {
            Node<K,V>[] tab = table; int n;
            if (tab == null || (n = tab.length) == 0) {
                n = (sc > c) ? sc : c;
                if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                    try {
                        if (table == tab) {
                            @SuppressWarnings("unchecked")
                            Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                            table = nt;
                            sc = n - (n >>> 2);
                        }
                    } finally {
                        sizeCtl = sc;
                    }
                }
            }
            else if (c <= sc || n >= MAXIMUM_CAPACITY)
                break;
            else if (tab == table) {
                int rs = resizeStamp(n);
                if (sc < 0) {
                    Node<K,V>[] nt;
                    if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                        sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                        transferIndex <= 0)
                        break;
                    if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                             (rs << RESIZE_STAMP_SHIFT) + 2))
                    transfer(tab, null);
            }
        }
    }

7 数据迁移流程

transfer(Node<K,V>[] tab, Node<K,V>[] nextTab)
7.1 获取旧tab的长度,赋给n,计算扩容区间的长度stride,最小值是16
7.2 新table为null,就初始化一个2倍的数组
7.2.1 把旧tab的长度,赋给tranferindex,这个变量用来计算stride的起始位置

7.3 创建一个 fwd 节点,用于占位。当别的线程发现这个槽位中是 fwd 类型的节点,则跳过这个节点
7.4 定义advance=true和finishing=false两个bool变量
7.4.1 advance 首次推进为 true,如果等于 true,说明可以再次推进一个下标(i–),
如果是 false,那么就不能推进下标,需要将当前的下标处理完毕才能继续推进
7.4.2 finishing完成状态,如果是 true,就结束此方法

7.5 在死循环for (int i = 0, bound = 0;;)中进行迁移操作
7.5.1 for中的变量i,旧tab的下标,用于表示stride的最大值;变量bound,用于表示stride的最小值
7.5.2 在while(advance)循环中,判断是否继续推进;首次进入while中时,会计算stride扩容区间的最大和最小下标

7.6 下面进行一系列if判断扩容是否结束
7.6.1 完成了扩容时,就会清理一些变量, 并重新计算sizeCtl扩容阈值
7.6.2 未完成扩容,获取老 tab i 下标位置的元素f,如果是 null,就使用 fwd 占位
7.6.3 未完成扩容,老 tab i下标元素f的hash,如果是-1,就advance=true,说明别的线程已经处理过了,再次推进到下个元素
7.6.4 未完成扩容,且不是fwd,那么表示该元素下面挂了链表或者树,链表和树的数据迁移,请详见7.7 链表和树的数据迁移流程

7.7 链表和树的数据迁移流程
7.7.1 给元素f加锁,synchronized (f)
7.7.2 在对链表和树进行迁移时,会将他们打散,按照f.hash & n的结果0和1,分成两组,既结果为0分在低位桶,结果为1分在高位桶(n是老tab的长度)
7.7.3 元素f的hash>0,表示下面挂的是链表,ln = new Node<K,V>(ph, pk, pv, ln); 在链表头部加入新的节点,生成低位桶链表,高位桶链表一样
7.7.4 setTabAt(nextTab, i, ln)将低位桶链表放到新tab中,setTabAt(nextTab, i + n, hn)将高位桶链表放到新tab中,
setTabAt(tab, i, fwd)将老tab的元素设为占位符, advance = true继续向前推进
7.7.5 元素f是树时,老的树会被分成两组,当两组的节点数量<=6时,将分组中的节点转成链表,反之,创建一个新的树
7.7.6 ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :(hc != 0) ? new TreeBin<K,V>(lo) : t; 转链表或者转树
7.7.7 setTabAt(nextTab, i, ln)将低位树放到新tab中,后面和7.7.4一样

    private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            transferIndex = n;
        }
        int nextn = nextTab.length;
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            while (advance) {
                int nextIndex, nextBound;
                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))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (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<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

ConcurrentHashMap在jdk1.7和1.8中的区别

1. 整体结构不同

1.7:底层数据结构是数组+链表
1.8:底层数据结构是数组+链表+红黑树,采用Synchronized + CAS 来保证线程安全

2. put()方法不同

1.7:先定位Segment,再定位桶,put全程加锁(自旋锁),没有获取锁的线程提前找桶的位置,并最多自旋64次获取锁,超过则挂起。
1.8:由于移除了Segment,类似HashMap,可以直接定位到桶,拿到首节点后进行判断,1.为空则CAS插入;2.为-1则说明在扩容,则参与一起扩容;3. 非空且不为-1,则加锁put

3. get()方法

基本类似,由于value声明为volatile,保证了修改的可见性,因此不需要加锁。

4. 扩容方法不同

1.7:跟HashMap步骤一样,只不过是搬到单线程中执行,避免了HashMap在1.7中扩容时死循环的问题,保证线程安全(1.new个2倍数组 2.遍历old数组节点搬去新数组)。
1.8:支持并发扩容,HashMap扩容在1.8中由头插改为尾插(为了避免死循环问题),ConcurrentHashmap也是,迁移也是从尾部开始,扩容前在桶的头部放置一个hash值为-1的节点,这样别的线程访问时就能判断是否该桶已经被其他线程处理过了。

5. size()方法不同

1.7:计算两次,如果不变则返回计算结果,若不一致,则锁住所有的Segment求和。
1.8:会维护一个baseCount属性用来记录结点数量,每次进行put操作之后都会CAS自增baseCount。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值