concurrentHashMap简析

concurrentHashMap是java.util.concurrent包下的一个类,该包中提供了非常多的容器,能够有效支持并发操作。而其中concurrentHashmap则是比较常用的一个集合,可以简单理解为一个线程安全的HashMap,至于concurrentHashmap与Hashtable的具体区别,我们在接下来说。


HashMap与ConcurrentHashMap

对于单线程程序来说,ConcurrentHashMap与HashMap使用起来没有区别,但是对于多线程,ConcurrentHashMap则是线程安全的,而Hashtable也是线程安全的,但是二者性能有很大差别,其原因在于底层结构的实现。

  • Hashtable   对整个集合实现了一个锁,当一个线程访问时,其他线程不可进行访问
  • ConcurrentHashMap   实现了分段锁,为集合中的每一段实现一个锁,当不同线程访问不同段时,不会出现阻塞情况

ConcurrentHashMap分段锁的实现

在HashMap中,底层结构使用为Entry数组+链表(JDK1.8之后优化为Entry数组+红黑树),而在ConcurrentHashMap中,Java为其增添了另一个数组Segment,每一个ConcurrentHashMap集合中有一定数量的segment(不可扩容,手动设置初始容量),而在每个segment数组中,又储存了一个Entry数组。我们为每一个segment配备一把锁,此时ConcurrentHashMap被多个segment分为多段,每段都有一把锁,当多线程访问时,只有多个线程访问同一段segment时,才会出现阻塞情况,否则会各自获取到所访问数据段的锁。

通过这种方法,有效提高了多线程访问效率,能够保证当访问数据在同一段时,可以不用阻塞等待,而实际上,我们也可以将ConcurrentHashMap看作为多个Hashtable的集合,每一个segment可以看为一个Hashtable,只有当访问同一个hashtable时,才会进行锁的竞争。如下图(图来源于网上,侵删)

可以看到,与之前HashMap最大区别即为多了一个Segment数组,我们源码分析就从segment数组开始


ConcurrentHashMap源码分析(JDK1.9)

首先我们找到源码中的Segment类(concurrentHashMap中主要为三个实体类,Node,segment和concurrentHashmap)

 static class Segment<K,V> extends ReentrantLock implements Serializable {
        private static final long serialVersionUID = 2249069246763182397L;
        final float loadFactor;
        Segment(float lf) { this.loadFactor = lf; }
    }

可以看到,这里loadFactor为负载因子,在segment初始化时就自动完成,而在JDK1.8之后,取消了segment这个字段,改用HashEntry数组table来代替segment进一步减少并发冲突。

static final class MapEntry<K,V> implements Map.Entry<K,V> {
        final K key; // non-null
        V val;       // non-null
        final ConcurrentHashMap<K,V> map;
        MapEntry(K key, V val, ConcurrentHashMap<K,V> map) {
            this.key = key;
            this.val = val;
            this.map = map;
        }
        public K getKey()        { return key; }
        public V getValue()      { return val; }
        public int hashCode()    { return key.hashCode() ^ val.hashCode(); }
        public String toString() {
            return Helpers.mapEntryToString(key, val);
        }

        public boolean equals(Object o) {
            Object k, v; Map.Entry<?,?> e;
            return ((o instanceof Map.Entry) &&
                    (k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
                    (v = e.getValue()) != null &&
                    (k == key || k.equals(key)) &&
                    (v == val || v.equals(val)));
        }

        /**
         * Sets our entry's value and writes through to the map. The
         * value to return is somewhat arbitrary here. Since we do not
         * necessarily track asynchronous changes, the most recent
         * "previous" value could be different from what we return (or
         * could even have been removed, in which case the put will
         * re-establish). We do not and cannot guarantee more.
         */
        public V setValue(V value) {
            if (value == null) throw new NullPointerException();
            V v = val;
            val = value;
            map.put(key, value);
            return v;
        }
    }

可以看到,在HashMap内部中维护了一个concurrentHashMap的实例,当我们将Key,Value存入时,我们会将这个数据放入HashMap中,然后这个HashMap存入concurrentHashMap中,详见这里的构造函数。

Hash函数进行散列过程:首先进行一次散列,将数据均匀散列到table中,然后进行再哈希,进入hashEntry中。用Put来举例

 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; K fk; V fv;
            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)))
                    break;                   // no lock when adding to empty bin
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else if (onlyIfAbsent && fh == hash &&  // check first node
                     ((fk = f.key) == key || fk != null && key.equals(fk)) &&
                     (fv = f.val) != null)
                return fv;
            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);
                                    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;
                            }
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

过程大致为,先对键值对进行判断,然后将键值对储存如一个table中,然后将table放入数组,之后进行一些散列处理。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值