轻松理解ConcurrentHashMap的原理

ConcurrentHashMap是一种线程安全的hashmap,相对于HashTable, 它拥有更高的并发性.
现在,我们就来分析一下在JDK1.8下的ConcurrentHashMap的实现及原理:

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());//计算哈希值
        if ((tab = table) != null && (n = tab.length) > 0 &&
            //根据哈希值从table数组中获取到对应哈希桶中的头节点,未找到头结点返回null
            (e = tabAt(tab, (n - 1) & h)) != null) {
            //与头节点的哈希值进行比对,如果相等则返回头结点的val
            if ((eh = e.hash) == h) {
                //与头结点的key再进行比对
                if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                    return e.val;
            }
            //hash值为负值表示正在扩容,这个时候查的是ForwardingNode的find方法来定位到nextTable来
            //eh=-1,说明该节点是一个ForwardingNode,正在迁移,此时调用ForwardingNode的find方法去nextTable里找。
            //eh=-2,说明该节点是一个TreeBin,此时调用TreeBin的find方法遍历红黑树,由于红黑树有可能正在旋转变色,所以find里会有读写锁。
            else if (eh < 0) {
                return (p = e.find(h, key)) != null ? p.val : null;
            }

            //eh>=0,说明该节点下挂的是一个链表,直接遍历该链表即可。
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;//未找到对应的值
    }

可以看到,在1.8中ConcurrentHashMap的get操作全程不需要加锁,这也是它比其他并发集合比如hashtable、用Collections.synchronizedMap()包装的hashmap;安全效率高的原因之一。

ConcurrentHashMap的get操作为什么不需要加锁呢?

大家都知道,线程安全常常是因为获取的共享变量不一致而导致的,而get方法中访问到的共享变量只有Node类型的数组table,

    transient volatile Node<K,V>[] table;

而volatile关键字可以保证共享变量的可见性, 即实时访问到其他线程修改后的数据. 那么,get方法不加锁的原因是因为table变量使用了volatile修饰么, 其实不然, volatile关键字对于基本类型的修改可以在随后对多个线程的读保持一致, 但是对于引用类型如数组,实体bean,仅仅保证引用的可见性,但并不保证引用内容的可见性
真正的原因是因为在Node类中val变量和next变量是用volatile修饰了, 也是因为此处保证了ConcurrentHashMap的get方法的线程安全

    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }

由以上可以看出
get操作全程不需要加锁是因为Node的成员val是用volatile修饰的数组用volatile修饰没有关系
数组用volatile修饰主要是保证在数组扩容的时候保证可见性。
那么, Node数组table为什么要用volatile修饰呢?
其实就是为了使得Node数组在扩容的时候对其他线程具有可见性而加的volatile

put()方法:

    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)
                //第一次添加元素时,初始化table数组
                tab = initTable();
            //添加到一个空的哈希桶时,通过CAS算法无锁插入数据
            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
            }
            //hash桶(tab[i])是fwd节点,表示正在扩容
            else if ((fh = f.hash) == MOVED)
                //扩容
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                //锁住哈希桶的头结点
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        //哈希值>=0说明是Node链表
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //新增元素的哈希值与头结点相同,则将值val替换为新值
                                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) {
                    //链表长度>=8时,转换成红黑树
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //哈希桶存储的元素个数+1
        addCount(1L, binCount);
        return null;
    }

从源码中看出,put方法在往空的哈希桶添加元素时,是采用CAS算法无锁插入的; 只有在发生哈希碰撞时,才会给哈希桶加上锁,锁是每个哈希桶的头结点.
在这里插入图片描述

remove()方法

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

    /**
     * Implementation for the four public remove/replace methods:
     * Replaces node value with v, conditional upon match of cv if
     * non-null.  If resulting value is null, delete.
     * <p>
     *  四个公共删除/替换方法的实现:用v替换节点值,条件是如果非空则匹配cv。如果结果值为null,请删除。
     * 
     */
    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;
            //table为空或者还未添加元素
            if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
                break;
            //正在扩容
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                boolean validated = false;
                //锁住头结点
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        //链表结构
                        if (fh >= 0) {
                            validated = true;
                            for (Node<K,V> e = f, pred = null;;) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    V ev = e.val;
                                    if (cv == null || cv == ev ||
                                        (ev != null && cv.equals(ev))) {
                                        oldVal = ev;
                                        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;
                            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;
                                    else if (t.removeTreeNode(p))
                                        setTabAt(tab, i, untreeify(t.first));
                                }
                            }
                        }
                    }
                }
                if (validated) {
                    if (oldVal != null) {
                        if (value == null)
                            addCount(-1L, -1);
                        return oldVal;
                    }
                    break;
                }
            }
        }
        return null;
    }

remove方法也是锁住哈希桶的头结点.依赖于CAS算法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值