ConcurrentHashMap中的一些方法的理解

这篇(可能不止这一篇)主要记录一下自己学习ConcurrentHashMap的情况.

主要分析一下put()(调用了putVal())与get()

0.一些常见的东西

这里说一说类中随处可见的几个东西

0.1 tabAt((Node<K,V>[] tab, int i)

见名知意,返回tab中索引为i的内容,volatile.

    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);
    }

0.2 casTabAt(Node<K,V>[] tab, int i,Node<K,V> c, Node<K,V> v)

首先看一下这个方法中调用的方法.看了一下别人的解释,大概意思是:针对Object对象进行CAS操作.对于o,原子性的设置其偏移地址为offset的值为x,当且仅当偏移offset处的内容为expected才进行如上操作.更新成功返回true,否则返回false.是一个本地方法.

然后回来理解一下这个方法,将tabi位置的元素替换为v,仅当i位置元素为c时才进行.

    static final <K,V> boolean             casTabAt(Node<K,V>[] tab, int i,
                                                    Node<K,V> c, Node<K,V> v) {
        return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    }

    public final native boolean compareAndSwapObject(Object o,long offset, 
                                                     Object expected, Object x);

0.3 static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v)

tab中i位置的元素设置为v,volatile.

    static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }

那这个常量U又是个啥?

我们翻到源码差不多最后的位置,可以看到一些声明,其中有一个是

private static final sun.misc.Unsafe U

Unsafe是sun.misc下的一个类,此部分的源码未开源.此类中绝大多数方法为native方法.

主要进行一些非常底层的操作,比如直接移动内存指针,听起来就很危险,建议不要使用.但是由于是相当底层的操作,所以效率肯定是相当高的.

0.4 几个常量

static final int MOVED     = -1; //FowardingNode的哈希值
static final int TREEBIN   = -2; //TreeBin的哈希值
static final int RESERVED  = -3; //ReservationNode的哈希值

以上三个类均是Node的子类.

 

1.存数据

我们存储数据时调用的是put(),put中则是()调用了putVal(),所以这里就看一下putVal().

1.1 putVal(K key, V value, boolean onlyIfAbsent)

 final V putVal(K key, V value, boolean onlyIfAbsent) {
        //不能存储null
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());//计算一下传入key的哈希值 ===================(1)
        int binCount = 0;

        //开始遍历底层数组
        for (Node<K,V>[] tab = table;;) {//tab赋值为底层数组table,相当于做了一个镜像
            Node<K,V> f; int n, i, fh;

            //table(存储数据的数组)为空就初始化table,见名知意
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();====================================================(2)

            //返回node中索引为(tab的length-1与key的hash进行与运算)的元素,赋值给f
            //相当于计算了f在tab中应该处于哪个位置
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //该位置为空的时候直接创建Node对象并插入i位置即可.
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    //确实进行修改了就中断循环
                    break;                   // no lock when adding to empty bin
                                             //向空位置加入元素不需要加锁
            }
            
            //fh赋值为f的哈希值,MOVED的值为-1.
            //大部分情况下哈希值不会为负值.
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);

            //剩下的情况就是这个位置有元素了.
            else {
                //声明一个类型为V的oldVal变量
                V oldVal = null;

                //用上面获取到的f作为锁,尺度相对Hashtable中(全部锁定)小得多了.
                //这里相当于只锁住了底层数组当前位置下的元素,而其他元素并未被锁定
                //所以其他位置的元素还可以同步进行putVal操作.
                synchronized (f) { 
                    //i位置的元素为f(原本在该位置的元素)
                    if (tabAt(tab, i) == f) {

                        //fh,即i位置元素的哈希值大于0
                        if (fh >= 0) {
                            //记录循环次数
                            binCount = 1;
    
                            //这里将f赋值给新声明的Node.完成一次循环binCount就+1
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;

                                //如果e的key与传入的key完全相同
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {

                                    //e的val就用前面声明的oldVal变量储存起来
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)

                                        //e的val替换为传入的value
                                        e.val = value;

                                    break;//中断循环
                                }
                                
                                //新声明Node,赋值为e
                                Node<K,V> pred = e;
                                //如果e的下一个为空
                                //即该节点已经是最后一个节点
                                if ((e = e.next) == null) {
                                    //下一个元素就是传入的key,value,该元素的next为空.
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;//中断循环
                                }
                            }
                        }
                        else if (f instanceof TreeBin) { //如果f是树
                            //以下是树插入元素的过程
                            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) {
                    //TREEIFY_THRESHOLD=8
                    //如果循环次数已经达到8,就意味着该位置有8个元素,就应该将当前节点转换为树.
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    //返回被替换掉的值
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //当前ConcurrentHashMap的元素数量+1
        addCount(1L, binCount);
        return null;
    }

然后看看标出来的两个方法spread()和initTable()

1.2 spread(int h)

  static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

逻辑很简单,就是进行重哈希.用来降低哈希冲突的可能性. 

1.3 initTable()

    private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc; 
        // 尚未被初始化的时候才进行初始化操作.
        while ((tab = table) == null || tab.length == 0) {
        
            // 参照源码的注释,这个sizeCtl的意义在于判断是否有其他线程在进行初始化操作.
            // 小于0的时候就证明有其他线程在进行操作,所以调用Thread.yield()让出时间片.
            // 大于0的时候不进行任何操作.
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
                                // 初始化的竞赛输了,原地踏步吧~

            // 这个compareAndSwapInt()与上面putVal()中casTabAt()
            // 调用的方法compareAndSwapObject目的相同.
            // 运行到这里,sc一定是大于等于0的.
            // 此时修改为-1,表示这个线程正在进行初始化.
            else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
                try {

                    //再次判断是否已经被初始化
                    if ((tab = table) == null || tab.length == 0) {

                        //sc大于0的话n就赋值为sc,否则赋值为DEFAULT_CAPACITY(16)
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")

                        //创建新的Node数组,数组容量为n,此时才进行真正的初始化.
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        //tab变成了刚刚创建的Node数组,table变成了tab
                        table = tab = nt;
                        // n - (n>>>2) = n - 1/4n = 3/4n = 0.75n
                        //0.75这个数字是不是很熟悉?没错,就是负载因子.
                        //大量使用位运算,有效提升了运算速度.
                        sc = n - (n >>> 2);
                    }
                } finally {
                    // 如果数组已经初始化,那么sc一定大于0
                    // 
                    sizeCtl = sc;
                }
                break;
            }
        }
        //初始化完成    
        return tab;
    }

方法的作用是在底层数组table未初始化的时候对其进行初始化.但是这个初始化显然不是简简单单的new一个数组.

 

2.获取数据

分析完了难度较高的存入数据,下面来看看相对简单的获取数据方法get(Object key).

    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //计算一下key的哈希
        int h = spread(key.hashCode());

        //底层数组不为空且key计算出的哈希所在位置不为空才进行接下来的操作
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (e = tabAt(tab, (n - 1) & h)) != null) {

            if ((eh = e.hash) == h) {
                //ek与key完全相同则直接返回e中的val
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }

            //上面是直接就可以找到的情况,即key恰好与其对应位置上的第一个元素的key完全相同.
            // 即key与其对应位置首节点的key恰好匹配.

            //eh小于0代表这是Node的子类,直接调用子类中重写的find()寻找.
            else if (eh < 0)
                return (p = e.find(h, key)) != null ? p.val : null;

            //这时候就从首节点的next开始依次寻找,直到找到.
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值