zoukankan      html  css  js  c++  java
  • 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做了很大的改进,这个就以后再说了。

  • 相关阅读:
    103. 二叉树的锯齿形层次遍历
    102. 二叉树的层次遍历
    94. 二叉树的中序遍历
    Redis和数据库 数据同步问题
    203. 移除链表元素
    19. 删除链表的倒数第N个节点
    237. 删除链表中的节点
    141. 环形链表
    2. 两数相加
    143. 重排链表
  • 原文地址:https://www.cnblogs.com/blogofjzq/p/9376102.html
Copyright © 2011-2022 走看看