美文网首页
ConcurrentHashMap源码分析(1.7)

ConcurrentHashMap源码分析(1.7)

作者: chase_ | 来源:发表于2020-08-15 23:11 被阅读0次

Key不能为null

数据结构

ConcurrentHashMap的底层数据结构是由Segment数组组成,而Segment的元素存储的是HashEntry的数组,HashEntry和HashMap的Entry的结构十分相似,都是包含hash,K,V,next四个属性

 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;
        }
ConcurrentHashMap底层结构.png

Segment数组长度代表并发度,Segment数组元素存储的是HashEntry数组。

put元素逻辑分析

public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        int hash = hash(key);
     //  j 用来定位元素应该放进那一个segment
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);
        return s.put(key, hash, value, false);
    }

通过(hash >>> segmentShift) & segmentMask计算元素应该防止那一个segment。
s = ensureSegment(j);这行代码其实是确保定位到的segment[j]已经初始化。

private Segment<K,V> ensureSegment(int k) {
        final Segment<K,V>[] ss = this.segments;
        long u = (k << SSHIFT) + SBASE; // raw offset
        Segment<K,V> seg;
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
            Segment<K,V> proto = ss[0]; // use segment 0 as prototype
            int cap = proto.table.length;
            float lf = proto.loadFactor;
            int threshold = (int)(cap * lf);
            HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
            if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                == null) { // recheck
                Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
                while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                       == null) {
                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                        break;
                }
            }
        }
        return seg;
    }

每个segment的初始化其实是以ss[0]为prototype原型的,ss[0]是在new ConCurrentHashMap的时候已经初始化了,这样做的目的是防止每个segment的初始化的时候都重新计算里面的table.length,loadFactor,threshold这些信息。

put的时候扩容

Segment数组不会扩容,segment初始化之后就不再变化了。只是对Segment数组里的table进行

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;
                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;
                                ++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;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
// 扩容
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
//解锁
                unlock();
            }
            return oldValue;
        }

Segment继承了ReentrantLock锁,在put的时候,是某个segment[i]进行加锁,从而保证线程的安全。上面的rehash扩容代码也是在加锁的代码里,所以不存在线程并发扩容的问题。

相关文章

网友评论

      本文标题:ConcurrentHashMap源码分析(1.7)

      本文链接:https://www.haomeiwen.com/subject/hnrzdktx.html