java并发编程之ConcurrentHashMap

  • ConcurrentHashMap简介
    为何要使用ConcurrentHashMap?
    Hashtable效率低下,方法阻塞。
    HashMap线程不安全,扩容并发情况下get死循环。
    ConcurrentHashMap采用分段锁技术提高并发性能。

    HashMap和Hashtable比较
    ①接口继承类
           HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。都同时实现了Map、Cloneable、Serializable。
    ②存储key-value
           HashMap支持key-value,null-null,key-null,null-value四种形式,Hashtable仅支持key-value(key和value都不为null这种形式),不能使用HashMap的get方法来判断是否存在某个键,而应该用containsKey()方法来判断。get存在null key或者不存在的key都返回null,无法判断是否存在键。
    ③线程安全性
           HashMap非线程安全的,而Hashtable几乎都是synchronized方法。HashMap可以通过synchronizedMap来进行处理,亦或者我们使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。
    ④初始容量和扩充容量
           Hashtable默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。
    ⑤hash散列算法
    Hashtable:
          int hash = key.hashCode();
          int index = (hash & 0x7FFFFFFF) % tab.length; //求模
    HashMap:
          static final int hash(Object key) {
               int h;
               // h = key.hashCode() 为第一步 取hashCode值
               // h ^ (h >>> 16)  为第二步 高位异或
               return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
          }
  • ConcurrentHashMap的结构
           ConcurrentHashMap = Segment + HashEntry。Segment继承了RentrantLock,它也是一种可重入锁,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和HashMap类似,是一种数组和链表结构。一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得与它对应的Segment锁。

    java并发编程之ConcurrentHashMap_第1张图片

    ConcurrentHashMap元素定位:
    segment定位:(hash >>> segmentShift) & segmentMask
    HashEntry定位:hash & (tab.length - 1)
    Integer.parseInt("0001111", 2) & 15,(0011111,0111111,1111111),只要低位一致,散列值均为15,冲突严重?
    hash值则通过hashcode再散列计算,目的是减少散列冲突,使元素能够均匀地分布在不同的Segment上,从而提高容器的存取效率,计算方法如下
        private static int hash(int h) {
            h += (h << 15) ^ 0xffffcd7d;
            h ^= (h >>> 10);
            h += (h << 3);
            h ^= (h >>> 6);
            h += (h << 2) + (h << 14);
            return h ^ (h >>> 16);
        }

    ConcurrentHashMap扩容:
           Segment的扩容判断比HashMap更合理,HashMap在插入元素后再判断是否扩容,如果到达阀值就进行扩容,但是很有可能扩容之后没有新元素插入,这时HashMap就进行了一次无效的扩容。Segment在插入元素前会先判断Segment里的HashEntry数组是否超过容量(threshold),如果超过阈值,则对数组进行扩容。在扩容的时候,首先会创建一个容量是原来容量两倍的数组,然后将原数组里的元素进行再散列后插入到新的数组里。为了高效,ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。

    ConcurrentHashMap size统计:
           先尝试2次通过不锁住Segment的方式来统计各个Segment大小,如果统计的过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小。
    public int size() {
            final Segment[] segments = this.segments;
            int size; // 统计出元素个数
            boolean overflow; // 超出Integer。MAX_VALUE
            long sum;         // 元素变动次数(put、remove和clear导致对应segement的count变化)
            long last = 0L;   // 存储上一轮元素变动次数
            int retries = -1; // 重试统计次数
            try {
                for (;;) {
                    if (retries++ == RETRIES_BEFORE_LOCK) { //是否达到2次
                        for (int j = 0; j < segments.length; ++j)
                            ensureSegment(j).lock(); // 达到阀值每段加锁统计
                    }
                    sum = 0L;
                    size = 0;
                    overflow = false;
                    for (int j = 0; j < segments.length; ++j) {
                        Segment seg = segmentAt(segments, j);
                        if (seg != null) {
                            sum += seg.modCount; //累加所有segment变动次数
                            int c = seg.count; //当前segment的元素个数
                            if (c < 0 || (size += c) < 0) //超出最大值
                                overflow = true;
                        }
                    }
                    if (sum == last) //上一轮的last值本轮统计sum一致,说明元素没有变动
                        break; //结束循环
                    last = sum;
                }
            } finally {
                if (retries > RETRIES_BEFORE_LOCK) { //超出重试统计次数,后需要解锁
                    for (int j = 0; j < segments.length; ++j)
                        segmentAt(segments, j).unlock();
                }
            }
            return overflow ? Integer.MAX_VALUE : size;
        }

你可能感兴趣的:(并发编程)