金三银四之ConcurrentHashMap剖根问底栏目(二)

前言

咱们话接前文,今天继续C~Map的剖根问底,有人在后台私信cas等的问题,后边会有专门的文章来分享这些知识点,毕竟每篇文章都要有自己的主题。不在主题里边的分享起来可能没完没了,导致主次错乱。还是附上我们的上一节文章连接:金三银四之ConcurrentHashMap剖根问底栏目(一)

1、ConcurrentHashMap的get方法

get方法因为是取值,熟悉多线程编程我们知道,大部分情况下读是不用加锁的,C-Map也没例外,get时并没有加锁,如果我们稍微多问个为什么,就会想到这个C~Map的get方法为什么没有从treenode节点获取数据,看我们下边的注释e.find方法其实就是关键。

public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //算出key的hash
        int h = spread(key.hashCode());
        //取出table,然后根据路由算法,取出存该key的node节点
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {
            //如果取出来的节点key的hash与当前key的hash相等,且值也相等
            //那么就是我们要找的值了
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            //
            else if (eh < 0)
                //这里是一个关键点,我们会发现为什么C~Map的get方法会没有
                //treenode节点的查询呢,其实是treenode继承了node节点,
                //也重写了find方法,所以这里直接调用find方法就行。
                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;
    }

2、ConcurrentHashMap的keyset方法

keyset也是常用的方法之一,获取到map中的key的集合,返回值和HashMap的不同,C-Map返回的是KeySetView对象,其源码及重要说明如下所示:

	public KeySetView<K,V> keySet() {
        KeySetView<K,V> ks;
        //返回的是KeySetView,如果当前的keySet为空,那么就new一个
        //看下面的静态内部类KeySetView
        return (ks = keySet) != null ? ks : (keySet = new KeySetView<K,V>(this, null));
    }
	public static class KeySetView<K,V> extends CollectionView<K,V,K>
        implements Set<K>, java.io.Serializable {
        
        KeySetView(ConcurrentHashMap<K,V> map, V value) {  // non-public
            super(map);
            this.value = value;
        }
		
		//省略部分代码
		
		//lambda表达式
		public void forEach(Consumer<? super K> action) {
            if (action == null) throw new NullPointerException();
            Node<K,V>[] t;
            if ((t = map.table) != null) {
            	//如果看Traverser的实现会发现,内部实现了一个for循环迭代器去
            	//遍历所有的node信息
                Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length);
                for (Node<K,V> p; (p = it.advance()) != null; )
                	//把key给到Consumer接口参数里
                	//这样我们使用的时候就能拿到key了
                    action.accept(p.key);
            }
        }
    }

3、ConcurrentHashMap中被使用频次非常高的tabAt方法

通过看源代码我们会发现该方法的主要作用就是:首先i为key的hash计算后的值,ASHIFT和ABASE在金三银四之ConcurrentHashMap剖根问底栏目(一)这篇文章中介绍过,获取key在tab中的偏移量,而计算偏移量的方法就是 i<< ASHIFT + ABASE, 剩下就好说了该句的意思就是:从主内存中获取存放该key的node。

    static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

4、ConcurrentHashMap的helpTransfer方法

该方法主要作用就是当节点是MOVED节点时就会调用这个方法。如果不懂compareAndSwapInt方法请点击Unsafe类常见方法精讲
进行预习

    final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
        Node<K,V>[] nextTab; int sc;
        //如果当前的节点不为空、节点时ForwardingNode节点
        //且下个节点也不为空
        if (tab != null && (f instanceof ForwardingNode) &&
            (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
            //该方法是在tab的当前长度下生成扩容标识戳
            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成立之后就进行移动,其实这个方法很有讲究
                //SIZECTL偏移后的位置本身记录的就是sizeCtl的值
                //sc在上边一步又被赋值为sizeCtl
                //所以在这一步才能进行cas,要不就根本没有什么意义
                //cas本身是原子操作,直接将this的sizeCtl的值+1
                //这样在下次循环过来的时候会继续+1,只要不超过MAX_RESIZERS(65535)最大扩容数
                //
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                    transfer(tab, nextTab);
                    break;
                }
            }
            return nextTab;
        }
        return table;
    }

5、结语

道阻且长,行则将至,行而不辍,未来可期,加油。

原创不易,如果你觉得文章不错,对你的进步有那么一点帮助,那么就给个小心心,如果觉得文章非常对你的胃口,那么欢迎你关注我,或者关注个人的微信公众号 程序猿每日分享,这里有资源,有内推,有和你志同道合的朋友,咱们一起打怪升级。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序猿每日分享

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值