ConcurrentHashMap(jdk1.6)部分源码解析以及和HashMap的对比

首先看储存数据结构

HashMap:transient Entry[] table;

ConcurrentHashMap:final Segment<K,V>[] segments;

这里的每个Segment就相当于一个小的HashMap,而且因为是final修饰的,只能赋值一次,默认初始容量16。

static final class Segment<K,V> extends ReentrantLock implements Serializable {

看到Segment的类修饰继承等情况,发现它继承了ReentrantLock,由此可以想到,Segment是通过重入锁来保证线程安全。

到这里大致可以归纳出,ConcurrentHashMap通过重入锁保证线程安全,通过分段加锁减小锁粒度。

继续进入Segment

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

这里的和HashMap的Entry[] table差不多,通过volatile关键字保证可见性。也是通过数组加链表来实现HashMap。

来看最重要的put方法

public V put(K key, V value) {
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key.hashCode());
        return segmentFor(hash).put(key, hash, value, false);
    }

这里和HashMap有个区别,HashMap是key不为空,这里是value不为空。

看segmentFor(hash)

final Segment<K,V> segmentFor(int hash) {
        return segments[(hash >>> segmentShift) & segmentMask];
    }

hash定位返回一个Segment,继续看Segment的put

 V put(K key, int hash, V value, boolean onlyIfAbsent) {
            lock();
            try {
                int c = count;
                if (c++ > threshold) // ensure capacity
                    rehash();
                HashEntry<K,V>[] tab = table;
                int index = hash & (tab.length - 1);
                HashEntry<K,V> first = tab[index];
                HashEntry<K,V> e = first;
                while (e != null && (e.hash != hash || !key.equals(e.key)))
                    e = e.next;

                V oldValue;
                if (e != null) {
                    oldValue = e.value;
                    if (!onlyIfAbsent)
                        e.value = value;
                }
                else {
                    oldValue = null;
                    ++modCount;
                    tab[index] = new HashEntry<K,V>(key, hash, first, value);
                    count = c; // write-volatile
                }
                return oldValue;
            } finally {
                unlock();
            }
        }

因为继承了重入锁,lock() unlock()没什么好说的,里面的代码也很简单易懂,和jdk1.8之前的HashMap相似,定位数组,在链表中找是否存在,存在改掉原来的value,不存在新建一个。当count大于threshold时rehash()和HashMap的resize对应。

比较有意思的是两个需要对整个Map进行操作的函数containsValue和size。

看看size。

public int size() {
        final Segment<K,V>[] segments = this.segments;
        long sum = 0;
        long check = 0;
        int[] mc = new int[segments.length];
        // Try a few times to get accurate count. On failure due to
        // continuous async changes in table, resort to locking.
        for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) {
            check = 0;
            sum = 0;
            int mcsum = 0;
            for (int i = 0; i < segments.length; ++i) {
                sum += segments[i].count;
                mcsum += mc[i] = segments[i].modCount;
            }
            if (mcsum != 0) {
                for (int i = 0; i < segments.length; ++i) {
                    check += segments[i].count;
                    if (mc[i] != segments[i].modCount) {
                        check = -1; // force retry
                        break;
                    }
                }
            }
            if (check == sum)
                break;
        }
        if (check != sum) { // Resort to locking all segments
            sum = 0;
            for (int i = 0; i < segments.length; ++i)
                segments[i].lock();
            for (int i = 0; i < segments.length; ++i)
                sum += segments[i].count;
            for (int i = 0; i < segments.length; ++i)
                segments[i].unlock();
        }
        if (sum > Integer.MAX_VALUE)
            return Integer.MAX_VALUE;
        else
            return (int)sum;
    }

因为size的同时可能伴随着修改,如put,remove等,所以它有一个检验机制,连续两次计算sum和check看有没有修改,如果两次都相等的话说明现在的值就是真正的size,如果检验失败,进入最麻烦的一步,把所有Segment上锁,计算size,再解锁。

在jdk1.8之后,对ConcurrentHashMap做了很大的改进,这个就以后再说了。

posted @ 2018-07-27 10:32  蒋曾  阅读(156)  评论(0编辑  收藏  举报