java8 ConcurrentHashMap源码学习(2)

java8 ConcurrentHashMap源码学习2


上文: java8 ConcurrentHashMap源码学习.

ConcurrentHashMap

之前发现常用的remove方法还有helptransfer并没有整理出来, 这里把学习心得整理一下, 顺便把get也贴上来

remove

其实这里跟put一样也是直接引用另一个方法

public V remove(Object key) {
        return replaceNode(key, null, null);
    }

这里看到用了replaceNode方法入参是key, null, null
下面的代码里可以看见这三个参数的名称
key就是map中的key, value就是要替换的值, cv就是要替换的key的value
如果value为空, 那么那么就是替换key的value而不是做删除
如果cv为空那么就不判断oldValue直接进入替换或者删除操作, 否则判断cv是否与oldValue相等

final V replaceNode(Object key, V value, Object cv) {
        int hash = spread(key.hashCode());
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
                break;
            // MOVED代表当前有迁移工作, 加入迁移
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                // 一个标志位, 标志是否有进行替换或者删除工作
                boolean validated = false;
                synchronized (f) {
                	// 确保上锁前后数据一致
                    if (tabAt(tab, i) == f) {
                    	// 置于这里为什么fh>=0才是链表, 因为在构造红黑树的时候
                    	// 构造函数会将hash变量设置为TREEBIN, 而TREEBIN的值是-2
                        if (fh >= 0) {
                            validated = true;
                            for (Node<K,V> e = f, pred = null;;) {
                                K ek;
                                // 找到这个key
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    V ev = e.val;
                                    // 上文提到的cv判断
                                    if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                        oldVal = ev;
                                        // 上文提到的value判断替换或者删除
                                        if (value != null)
                                            e.val = value;
                                        // 删除的不是链表头
                                        else if (pred != null)
                                            pred.next = e.next;
                                        // 删除链表头
                                        else
                                            setTabAt(tab, i, e.next);
                                    }
                                    break;
                                }
                                pred = e;
                                if ((e = e.next) == null)
                                    break;
                            }
                        }
                        else if (f instanceof TreeBin) {
                            validated = true;
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> r, p;
                            // 判断红黑树是否存在这个key
                            if ((r = t.root) != null &&
                                (p = r.findTreeNode(hash, key, null)) != null) {
                                V pv = p.val;
                                if (cv == null || cv == pv ||
                                    (pv != null && cv.equals(pv))) {
                                    oldVal = pv;
                                    if (value != null)
                                        p.val = value;
                                    // 这里removeTreeNode返回的bool不是代表删除的成功与否
                                    // 这里返回的是是否需要将红黑树转换为链表
                                    // 下文会写到这个方法
                                    else if (t.removeTreeNode(p))
                                    	// 用untreeify方法将红黑树链表化
                                        setTabAt(tab, i, untreeify(t.first));
                                }
                            }
                        }
                    }
                }
                // 已经操作成功
                if (validated) {
                    if (oldVal != null) {
                    	// 这里表示这是个删除操作, 将table中的元素计数器-1
                        if (value == null)
                            addCount(-1L, -1);
                        return oldVal;
                    }
                    break;
                }
            }
        }
        return null;
    }

removeTreeNode

主要讲一下返回的true和false, 红黑树操作按下不表, (因为👴还不太清楚, 下次一定学

final boolean removeTreeNode(TreeNode<K,V> p) {
            TreeNode<K,V> next = (TreeNode<K,V>)p.next;
            TreeNode<K,V> pred = p.prev;  // unlink traversal pointers
            TreeNode<K,V> r, rl;
            // 在TreeBin里面是有first属性的, 因为在treeifyBin这个方法中
            // 先构成双链表, 然后传入TreeBin的构造器中构造红黑树
            if (pred == null)
            	// 这里pred=null代表着要删除的p是第一个结点, first后移
                first = next;
            else
            	// 否则pred指向p的下一个
                pred.next = next;
            if (next != null)
            	// 双链表操作
                next.prev = pred;
            // 特判first为空的情况, 直接返回true表示将红黑树链表化, 出来的当然也是null
            if (first == null) {
                root = null;
                return true;
            }
            // 这里是判断红黑树大小, 没有采用结点个数感觉挺奇怪的
            // 因为就算红黑树中有十个结点也可以构成true的情况, 我下面贴个图
            if ((r = root) == null || r.right == null || // too small
                (rl = r.left) == null || rl.left == null)
                return true;
            // 给红黑树上锁进行删除操作, 这下面涉及的就是红黑树的删除操作, 双链表操作上面做完了
            lockRoot();
            try {
                TreeNode<K,V> replacement;
                TreeNode<K,V> pl = p.left;
                TreeNode<K,V> pr = p.right;
                if (pl != null && pr != null) {
                    TreeNode<K,V> s = pr, sl;
                    while ((sl = s.left) != null) // find successor
                        s = sl;
                    boolean c = s.red; s.red = p.red; p.red = c; // swap colors
                    TreeNode<K,V> sr = s.right;
                    TreeNode<K,V> pp = p.parent;
                    if (s == pr) { // p was s's direct parent
                        p.parent = s;
                        s.right = p;
                    }
                    else {
                        TreeNode<K,V> sp = s.parent;
                        if ((p.parent = sp) != null) {
                            if (s == sp.left)
                                sp.left = p;
                            else
                                sp.right = p;
                        }
                        if ((s.right = pr) != null)
                            pr.parent = s;
                    }
                    p.left = null;
                    if ((p.right = sr) != null)
                        sr.parent = p;
                    if ((s.left = pl) != null)
                        pl.parent = s;
                    if ((s.parent = pp) == null)
                        r = s;
                    else if (p == pp.left)
                        pp.left = s;
                    else
                        pp.right = s;
                    if (sr != null)
                        replacement = sr;
                    else
                        replacement = p;
                }
                else if (pl != null)
                    replacement = pl;
                else if (pr != null)
                    replacement = pr;
                else
                    replacement = p;
                if (replacement != p) {
                    TreeNode<K,V> pp = replacement.parent = p.parent;
                    if (pp == null)
                        r = replacement;
                    else if (p == pp.left)
                        pp.left = replacement;
                    else
                        pp.right = replacement;
                    p.left = p.right = p.parent = null;
                }

                root = (p.red) ? r : balanceDeletion(r, replacement);

                if (p == replacement) {  // detach pointers
                    TreeNode<K,V> pp;
                    if ((pp = p.parent) != null) {
                        if (p == pp.left)
                            pp.left = null;
                        else if (p == pp.right)
                            pp.right = null;
                        p.parent = null;
                    }
                }
            } finally {
            	// 解锁
                unlockRoot();
            }
            // 做红黑树的结构检查
            assert checkInvariants(root);
            return false;
        }

Alt
图贴上面了, 如果这一步理解有误, 希望评论区可以指出

helpTransfer

算是最短的方法了吧??? 因为逻辑跟transfer里面差不多, 所以上次就没去搭理

    final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        // 判断当前tab不为空, 并且传入结点正在迁移, 获得扩容好的nextTable
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            // 根据tab.length算出一个高十六位负数
            int rs = resizeStamp(tab.length);
            // 循环判断获得的数据是否不变, sizeCtl<0则说明正在迁移
            while (nextTab == nextTable && table == tab &&
                   (sc = sizeCtl) < 0) {
                // 迁移工作已经完成, 退出循环
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || transferIndex <= 0)
                    break;
                // 线程参与迁移, cas对sc+1表示参与迁移的线程数+1
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

get

    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        int h = spread(key.hashCode());
        // cas获取当前的key所在的桶, 保证这个桶的数据是最新的
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            // 判断桶的第一个结点
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            // 这里表示是红黑树
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;
            // 循环查找
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

之前find点进去还是链表的操作, 吓了一跳, 结果发现不是在TreeNode里面的, 下面才是真正使用的find,
堂堂正正的红黑树搜索

Node<K,V> find(int h, Object k) {
            return findTreeNode(h, k, null);
        }

后记

没有后记了, 如果还有下一篇再写这篇后记

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 8中的ConcurrentHashMap是线程安全的哈希表实现,它支持并发读写操作而不需要全局锁定。它的实现原理主要有以下几个方面: 1. 分段锁策略:ConcurrentHashMap将整个数据结构分成若干个Segment(段),每个Segment维护着一个独立的散列桶数组。每个Segment内部都有一个可重入锁,不同的线程可以同时访问不同的Segment,从而实现并发读写的能力。 2. 散列桶数组:ConcurrentHashMap使用散列桶数组来存储键值对。每个散列桶上都有一个链表,用于解决哈希冲突。当多个键映射到同一个散列桶时,它们会被链接到同一个链表上。 3. CAS操作:ConcurrentHashMap使用CAS(Compare and Swap)操作来保证并发更新操作的原子性。CAS是一种无锁算法,它通过比较内存中的值与期望值是否相等来确定是否更新。如果相等,则执行更新操作;否则,重新尝试。 4. 扩容机制:ConcurrentHashMap在插入新元素时,如果当前Segment的负载因子(即链表长度)超过阈值,则会触发扩容操作。扩容时,会创建一个新的散列桶数组,并将原来的键值对重新分配到新的散列桶中。这个过程可以通过加锁来保证线程安全。 总的来说,ConcurrentHashMap通过分段锁策略、散列桶数组、CAS操作和扩容机制来实现线程安全的并发读写操作。它在多线程环境下能够提供较好的性能和可伸缩性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值