ConcurrentHashMap原理

本文ConcurrentHashMap源码基于JDK1.7

1、ConcurrentHashMap初始化

创建concurrentHashMap实例

// 默认情况下初始容量是16,加载因子0.75,并发级别16
public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) {
        if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
            throw new IllegalArgumentException();
            // Segments数组最大容量为65536
        if (concurrencyLevel > MAX_SEGMENTS)
            concurrencyLevel = MAX_SEGMENTS;
        // Find power-of-two sizes best matching arguments
        // 找到一个大于并发级别的最小的2的n次方的数,作为Segments数组的长度
        int sshift = 0;
        int ssize = 1;
        while (ssize < concurrencyLevel) {
            ++sshift;
            ssize <<= 1;
        }
        this.segmentShift = 32 - sshift; //segmentShift =28
        this.segmentMask = ssize - 1; //ssize  = 16 ,segment掩码值15
        if (initialCapacity > MAXIMUM_CAPACITY)// 2的30次方
            initialCapacity = MAXIMUM_CAPACITY;
        int c = initialCapacity / ssize;
        if (c * ssize < initialCapacity)
            ++c;
        int cap = MIN_SEGMENT_TABLE_CAPACITY; // cap=2
        while (cap < c)
            cap <<= 1;
        // create segments and segments[0]
        //初始化Segment[0] ,加载因子0.75  threshold阈值为1,(阈值达到总容量的3/4是HashEntry会进行扩容)
        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]; // 初始化Segment数组
        //UNSAFE 操作,把s0元素装进segment[0]里,其它segment会“延迟初始化”
        UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
        this.segments = ss;
    }
    

2、put操作

    @SuppressWarnings("unchecked")
    public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
         // 计算key的hash值
        int hash = hash(key);
        // 将hash值无符号右移28位,并对16进行取模,定位segment的位置
        int j = (hash >>> segmentShift) & segmentMask;
        // 确保segment不为空
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
             // 如果Segment索引所在位置的HashEntry数组为空,则以s0为原型进行填充
            s = ensureSegment(j);
            // 这里是核心方法,定位到Segment后还需要定位HashEntry数组里的某个HashEntry
        return s.put(key, hash, value, false);
    }

2.1、s.put(key, hash, value, false)方法详解

 final V put(K key, int hash, V value, boolean onlyIfAbsent) {
 		// 已经定位到了Segment,现在开始定位HashEntry。如果发生竞争,没有获取到“分段锁”,当前线程会一直循环尝试获取锁,知道获取锁后再返回,如果key值不存在则创建并返回一个HashEntry节点,如果存在则返回null
 		// Segment 继承了ReentrantLock锁,在插入的时候必须获取锁,可以看到CurrentHashMap之所以比HashTable高效,是因为插入的时候使用了“分段锁”,锁的粒度更小。
            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[]元素,并且返回数组的头节点
                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;
                                // 表示map被修改的次数
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                        // 如果获取锁时,返回的node不为空, 采用头插法
                            node.setNext(first);
                        else
                        // 否则创建新的HashEntry节点
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        // 如果大于阈值,HashEntry数组会扩容到两倍,Segments是不会进行扩容的
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

2.2、rehash 操作

如果向table表再添加一个元素达到了阈值,也就是总容量的3/4,则会进行扩容操作。值得注意的是ConcurrentHashMap是先扩容,再进行插入。

 private void rehash(HashEntry<K,V> node) {
            /*
             * Reclassify nodes in each list to new table.  Because we
             * are using power-of-two expansion, the elements from
             * each bin must either stay at same index, or move with a
             * power of two offset. We eliminate unnecessary node
             * creation by catching cases where old nodes can be
             * reused because their next fields won't change.
             * Statistically, at the default threshold, only about
             * one-sixth of them need cloning when a table
             * doubles. The nodes they replace will be garbage
             * collectable as soon as they are no longer referenced by
             * any reader thread that may be in the midst of
             * concurrently traversing table. Entry accesses use plain
             * array indexing because they are followed by volatile
             * table write.
             */
            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];
            int sizeMask = newCapacity - 1;
            for (int i = 0; i < oldCapacity ; i++) {
                HashEntry<K,V> e = oldTable[i];
                if (e != null) {
                    HashEntry<K,V> next = e.next;
                    int idx = e.hash & sizeMask;
                    if (next == null)   //  Single node on list
                        newTable[idx] = e;
                    else { // Reuse consecutive sequence at same slot
                    // 消除不必要的节点创建,因为它们的next节点并没有发生变化,同一槽中的hash值都是一样的,因此可以整体迁移 ,其余的则进行克隆,在默认阈值下,大概只有1/6的节点需要克隆。
                        HashEntry<K,V> lastRun = e;
                        int lastIdx = idx;
                        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;
                        // Clone remaining nodes 
                        // 克隆剩下的节点
                        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];
                            // 将需要移动的节点,以头插法形式,插入到其它slot槽上
                            newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                        }
                    }
                }
            }
            int nodeIndex = node.hash & sizeMask; // add the new node
            // 头插法, volatile语义级别的写入,node就是新增加的节点
            node.setNext(newTable[nodeIndex]);
            newTable[nodeIndex] = node;
            table = newTable;
        }

get操作

查看源代码看到,现根据key计算hash码,定位到Segment位置,然后计算偏移值定位到table的位置,最后循环遍历HashEntry链表,比较key值,如果相同则返回value。可以发现,整个过程都没有加锁,获取值的过程都是Volatile语义级别的操作。

    public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        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 e.value;
            }
        }
        return null;
    }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ConcurrentHashMap是一个线程安全的HashMap,它的原理是通过使用一种叫做分段锁(Segment)的机制来实现并发安全。它将整个数据结构分成了多个段(Segment),每个段都是一个独立的HashMap,拥有自己的锁。这样,在并发操作时,不同的线程可以同时访问不同的段,从而提高了并发性能。每个段内部的操作仍然是非线程安全的,但由于不同的线程访问的段是不同的,所以并发操作不会产生竞争。这种设计方式既保证了线程安全,同时也提高了并发性能。 在ConcurrentHashMap中,每个段(Segment)实际上是一个类似于HashMap的数据结构,它包含了一个数组(Node[])来存储键值对。每个节点(Node)实际上也是一个链表的头节点,用来解决哈希冲突。每个节点包含了键、值以及下一个节点的引用。而且,ConcurrentHashMap中使用了volatile修饰的节点数组,保证了对数组的读写操作的可见性。 当进行插入、删除或者查找操作时,ConcurrentHashMap会根据键的哈希值来选择对应的段,并对该段进行加锁,保证同一段内的操作的原子性。这样,不同的线程可以同时对不同的段进行操作,提高了并发性能。同时,ConcurrentHashMap还使用了一种叫做CAS(Compare And Swap)的机制来保证对节点数组的原子性操作。通过这些机制的组合,ConcurrentHashMap实现了高效的并发安全操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [ConcurrentHashMap原理详解(太细了)](https://blog.csdn.net/qq_42068856/article/details/126091526)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [ConcurrentHashMap 原理](https://blog.csdn.net/liuwg1226/article/details/119548439)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值