源码阅读之ConcurrentHashMap(JDK1.7)

收获:
1、缩小锁的力度,不同的段之间修改不影响
2、自旋锁,通过tryLock()尝试获取锁,拿不到的情况下,可以先去做别的事情
3unsafe操作,直接通过内存地址操作数据,提高性能,提供了一些可见性的获取操作
4、跨段操作通过count计数来考虑别的段有没有被修改过
5、size\containValue等跨段操作不通过对每个段都加锁,而是先进行3次不加锁操作,
   任意2次操作结果相同即为正确结果,若不同,再加锁操作
6、get\containKey等操作,由于key是final修饰的,不可被修改,可直接定位到Segment
   再定位到桶,循环匹配
7ConcurrentHashMap的结构:  
   ConcurrentHashMap->大于并发级别的最小2次幂个Segment数组->每个Segment包含
   一个threshold*loadFactor大小的HashEntry数组->每个数据都是一个HashEntry上的节点
8、rehash()   

HashMap实际上是一个线性数组,数组上的每个元素都是一个链表(俗称桶)。
ConcurrentHashMap则是对这个线下数组进行了划分,划分成若干个段(segment),在并发操作时,会对对应的段加锁。
HashEntry:用来封装映射表的键 / 值对;Segment 用来充当锁的角色,每个 Segment 对象守护整个散列映射表的若干个桶。每个桶是由若干个 HashEntry 对象链接起来的链表。一个 ConcurrentHashMap 实例中包含由若干个 Segment 对象组成的线性数组。每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。

构造器

public ConcurrentHashMap(int initialCapacity,
                             float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
        //并发级别控制,默认16,最大1<<16
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        //ssize左移次数
        int sshift = 0;
        //segement大小,数值为大于并发级别的最小2次幂
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        //segmentShift和segmentMask这两个变量是用来定位Segment的
        this.segmentShift = 32 - sshift;
        this.segmentMask = ssize - 1;
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        //cap的数值为 (cap × ssize > initialCapacity)满足这个情况下的最小2次幂  
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY;
        while (cap < c)
            cap <<= 1;
        //创建segments数组并初始化第一个Segment
        Segment<K,V> s0 =
            new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                             (HashEntry<K,V>[])new HashEntry[cap]);
        Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }

Segment

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

        private static final long serialVersionUID = 2249069246763182397L;

        /**
         * 在准备锁定段操作之前,可能阻塞获取之前在预扫描中tryLock的最大次数。
         * 在多处理器上,使用有限数量的重试可维护在定位节点时获取的高速缓存。
         */
        static final int MAX_SCAN_RETRIES =
            Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;

        /**
         * 每段维护的表,通过entryAt/setEntryAt访问元素,具有多线程可见性,即A线程修改了A的table,B线程是知晓的
         */
        transient volatile HashEntry<K,V>[] table;

        /**
         * 元素的数量,没有被volatile修饰,如果是可见性的读取,依然可以多线程可见
         */
        transient int count;

        /**
         * 元素变更的次数
         */
        transient int modCount;

        /**
         * 当表的大小超过此阈值时,将进行rehash()
         */
        transient int threshold;

        /**
         * 加载因子
         */
        final float loadFactor;

        Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
            this.loadFactor = lf;
            this.threshold = threshold;
            this.table = tab;
        }

        final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            //尝试去获取锁,如果获取不到,做一些准备工作
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                //找到hash对应的HashEntry桶的第一个元素
                HashEntry<K,V> first = entryAt(tab, index);
                for (HashEntry<K,V> e = first;;) {
                    //对应的桶的对一个元素不为空,用新元素替换
                    if (e != null) {
                        K k;
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                //该Segment修改计数器加一
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        //对应的桶的对一个元素不为空,在头部追加新元素
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        //超过阈值,进行重hash
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        //该Segment修改计数器加一
                        ++modCount;
                        //元素数量加一
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

        /**
         * rehash扩容
         */
        @SuppressWarnings("unchecked")
        private void rehash(HashEntry<K,V> node) {
            //新建一个桶数组,大小是原来的2倍
            HashEntry<K,V>[] oldTable = table;
            int oldCapacity = oldTable.length;
            int newCapacity = oldCapacity << 1;
            threshold = (int)(newCapacity * loadFactor);
            HashEntry<K,V>[] newTable =
                (HashEntry<K,V>[]) new HashEntry[newCapacity];
            //sizeMask用作定位索引
            int sizeMask = newCapacity - 1;
            for (int i = 0; i < oldCapacity ; i++) {
                HashEntry<K,V> e = oldTable[i];
                //如果桶不为null
                if (e != null) {
                    HashEntry<K,V> next = e.next;
                    int idx = e.hash & sizeMask;
                    //如果桶只有一个元素,直接将新桶指向旧桶地址
                    if (next == null)   
                        newTable[idx] = e;
                    else { 
                        HashEntry<K,V> lastRun = e;
                        int lastIdx = idx;
                        /**
                         * 从桶的第二个节点开始循环到最后一个节点,
                         * 如果最后一个节点的hash和桶的头结点不相等,
                         * 则将临时节点(lastRun)指向最后一个节点.
                         */
                        for (HashEntry<K,V> last = next;
                             last != null;
                             last = last.next) {
                            int k = last.hash & sizeMask;
                            if (k != lastIdx) {
                                lastIdx = k;
                                lastRun = last;
                            }
                        }
                        //将桶的最后一个节点给新桶引用
                        newTable[lastIdx] = lastRun;
                        /**
                         * 从桶的第一个节点循环到倒数第二个节点,并添加到新桶中
                         * 每次从头部添加新元素
                         */
                        for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                            V v = p.value;
                            int h = p.hash;
                            int k = h & sizeMask;
                            HashEntry<K,V> n = newTable[k];
                            newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                        }
                    }
                }
            }
            //
            int nodeIndex = node.hash & sizeMask;
            //node指向null
            node.setNext(newTable[nodeIndex]);
            //newTable[nodeIndex]指向node
            newTable[nodeIndex] = node;
            table = newTable;
        }

        /**
         * 在等待Segment获取锁的时间内,做一些准备工作
         */
        private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
            //获取该hash下的桶(即HashEntry[])的第一个HashEntry
            HashEntry<K,V> first = entryForHash(this, hash);
            HashEntry<K,V> e = first;
            HashEntry<K,V> node = null;
            //重试次数
            int retries = -1;
            while (!tryLock()) {
                HashEntry<K,V> f; 
                if (retries < 0) {
                    //如果想要put的这个key在当前Segment中不存在,新建一个HashEntry
                    if (e == null) {
                        if (node == null) 
                            node = new HashEntry<K,V>(hash, key, value, null);
                        retries = 0;
                    }
                    //如果想要put的这个key在当前Segment中存在,且位于桶的第一个节点
                    else if (key.equals(e.key))
                        retries = 0;
                    /**
                     *如果想要put的这个key在当前Segment中存在,但不在桶的第一个节点,
                     *将e指向桶的下一个节点,即该位置就是需要插入的位置
                     */ 
                    else
                        e = e.next;
                }
                //如果超过重试次数,直接拿锁,拿不到就阻塞
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }

                /**
                 * 如果retries=0且该hash位置的桶的第一个HashEntry已经被修改,
                 * 则将修改过后的值赋给e,重新循环
                 * 这里解释一下,retries=0表示每次新循环的第二次循环,
                 * 这里可以校验每次新循环的第一次循环做出的决定。
                 * 很好理解,因为我的桶变了,所以我要重新执行这段代码!
                 */
                else if ((retries & 1) == 0 &&
                         (f = entryForHash(this, hash)) != first) {
                    e = first = f;
                    retries = -1;
                }
            }
            return node;
        }

        /**
         * 用来获取锁
         */
        private void scanAndLock(Object key, int hash) {
            // similar to but simpler than scanAndLockForPut
            HashEntry<K,V> first = entryForHash(this, hash);
            HashEntry<K,V> e = first;
            int retries = -1;
            while (!tryLock()) {
                HashEntry<K,V> f;
                if (retries < 0) {
                    if (e == null || key.equals(e.key))
                        retries = 0;
                    else
                        e = e.next;
                }
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }
                else if ((retries & 1) == 0 &&
                         (f = entryForHash(this, hash)) != first) {
                    e = first = f;
                    retries = -1;
                }
            }
        }

        /**
         * 本质就是链表的删除,只是加了一些额外的操作而已
         */
        final V remove(Object key, int hash, Object value) {
            if (!tryLock())
                scanAndLock(key, hash);
            V oldValue = null;
            try {
                HashEntry<K,V>[] tab = table;
                int index = (tab.length - 1) & hash;
                //找到需要被删除的链表(桶),需要删除的是这个链表上的某个节点
                HashEntry<K,V> e = entryAt(tab, index);
                HashEntry<K,V> pred = null;
                while (e != null) {
                    K k;
                    HashEntry<K,V> next = e.next;               
                    if ((k = e.key) == key ||
                        (e.hash == hash && key.equals(k))) {
                        V v = e.value;
                        if (value == null || value == v || value.equals(v)) {
                            if (pred == null)
                                //如果是头结点,通过相对于tab的内存偏移量覆盖
                                setEntryAt(tab, index, next);
                            else
                                //如果不是头结点,通过相对于当前HashEntry的内存偏移量覆盖
                                pred.setNext(next);
                            ++modCount;
                            --count;
                            oldValue = v;
                        }
                        break;
                    }
                    //将临时节点指向下一个节点
                    pred = e;
                    e = next;
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

        final boolean replace(K key, int hash, V oldValue, V newValue) {
            if (!tryLock())
                scanAndLock(key, hash);
            boolean replaced = false;
            try {
                HashEntry<K,V> e;
                for (e = entryForHash(this, hash); e != null; e = e.next) {
                    K k;
                    if ((k = e.key) == key ||
                        (e.hash == hash && key.equals(k))) {
                        if (oldValue.equals(e.value)) {
                            e.value = newValue;
                            ++modCount;
                            replaced = true;
                        }
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return replaced;
        }

        final V replace(K key, int hash, V value) {
            if (!tryLock())
                scanAndLock(key, hash);
            V oldValue = null;
            try {
                HashEntry<K,V> e;
                for (e = entryForHash(this, hash); e != null; e = e.next) {
                    K k;
                    if ((k = e.key) == key ||
                        (e.hash == hash && key.equals(k))) {
                        oldValue = e.value;
                        e.value = value;
                        ++modCount;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

        final void clear() {
            lock();
            try {
                HashEntry<K,V>[] tab = table;
                for (int i = 0; i < tab.length ; i++)
                    setEntryAt(tab, i, null);
                ++modCount;
                count = 0;
            } finally {
                unlock();
            }
        }
    }

HashEntry

HashEntry用来封装具体的键值对,是个典型的四元组。volatile保证了ConcurrentHashmap读操作并不需要加锁。

static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;

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

        /**
         * Sets next field with volatile write semantics.  (See above
         * about use of putOrderedObject.)
         */
        final void setNext(HashEntry<K,V> n) {
            UNSAFE.putOrderedObject(this, nextOffset, n);
        }

        // Unsafe mechanics
        static final sun.misc.Unsafe UNSAFE;
        static final long nextOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class k = HashEntry.class;
                nextOffset = UNSAFE.objectFieldOffset
                    (k.getDeclaredField("next"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

Put

1.定位segment并确保定位的Segment已初始化

 public V put(K key, V value) {
        Segment<K,V> s;
        //concurrentHashMap不允许key/value为空
        if (value == null)
            throw new NullPointerException();
        //hash函数对key的hashCode重新散列,避免差劲的不合理的hashcode,保证散列均匀
        int hash = hash(key);
        //返回的hash值无符号右移segmentShift位与段掩码进行位运算,定位segment
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject          
             (segments, (j << SSHIFT) + SBASE)) == null) 
            s = ensureSegment(j);
        return s.put(key, hash, value, false);
    }

segmentShiftsegmentMask这两个全局变量的主要作用是用来定位Segment。

int j =(hash >>> segmentShift) & segmentMask

segmentMask:段掩码,假如segments数组长度为16,则段掩码为16-1=15;segments长度为32,段掩码为32-1=31。这样得到的所有bit位都为1,可以更好地保证散列的均匀性。

segmentMask = ssize - 1

segmentShift:2的sshift次方等于ssize,segmentShift=32-sshift。若segments长度为16,segmentShift=32-4=28;若segments长度为32,segmentShift=32-5=27。
而计算得出的hash值最大为32位,无符号右移segmentShift,则意味着只保留高几位(其余位是没用的),然后与段掩码segmentMask位运算来定位Segment。

2 << sshift = ssize
segmentShift = ssize - sshift

Get

 public V get(Object key) {
        Segment<K,V> s; 
        HashEntry<K,V>[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                /**
                 * value的修改,我们保证是线程安全的。key被final修饰且e.key,e.value
                 * 都是原子性的操作。         
                 */
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

containsKey

@SuppressWarnings("unchecked")
    public boolean containsKey(Object key) {
        Segment<K,V> s; // same as get() except no need for volatile value read
        HashEntry<K,V>[] tab;
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
                 e != null; e = e.next) {
                K k;
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return true;
            }
        }
        return false;
    }

Size

 public int size() {
        final Segment<K,V>[] segments = this.segments;
        int size;
        boolean overflow; 
        long sum;         
        long last = 0L;   
        int retries = -1; 
        try {
            for (;;) {
            /**
             *先尝试3次不对segment加锁,获取count,如果这3次中,有相邻2次获取的count是一样的,
             *那么说明在获取size的过程中,没有其他线程进行插入或删除,即该size是准确的。
             *否则,直接加锁,对每个segment进行count的计算。
             */
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation
                }
                sum = 0L;
                size = 0;
                overflow = false;
                for (int j = 0; j < segments.length; ++j) {
                    Segment<K,V> seg = segmentAt(segments, j);
                    if (seg != null) {
                        sum += seg.modCount;
                        int c = seg.count;
                       //溢出
                        if (c < 0 || (size += c) < 0)
                            overflow = true;
                    }
                }
                if (sum == last)
                    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;
    }

containsValue

看到containValue,是不是疑惑了:为啥不能直接通过循环获取Segment,再获取HashEntry,然后遍历整个桶,查看value是否变化。虽然HashEntry的value是volatile修饰的,但value的修改不是原子的操作。

public boolean containsValue(Object value) {
        // Same idea as size()
        if (value == null)
            throw new NullPointerException();
        final Segment<K,V>[] segments = this.segments;
        boolean found = false;
        long last = 0;
        int retries = -1;
        try {
            outer: for (;;) {
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation
                }
                long hashSum = 0L;
                int sum = 0;
                for (int j = 0; j < segments.length; ++j) {
                    HashEntry<K,V>[] tab;
                    Segment<K,V> seg = segmentAt(segments, j);
                    if (seg != null && (tab = seg.table) != null) {
                        for (int i = 0 ; i < tab.length; i++) {
                            HashEntry<K,V> e;
                            for (e = entryAt(tab, i); e != null; e = e.next) {
                                V v = e.value;
                                if (v != null && value.equals(v)) {
                                    found = true;
                                    break outer;
                                }
                            }
                        }
                        sum += seg.modCount;
                    }
                }
                if (retries > 0 && sum == last)
                    break;
                last = sum;
            }
        } finally {
            if (retries > RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    segmentAt(segments, j).unlock();
            }
        }
        return found;
    }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值