通过与 Jira 对比,让您更全面了解 PingCode

  • 首页
  • 需求与产品管理
  • 项目管理
  • 测试与缺陷管理
  • 知识管理
  • 效能度量
        • 更多产品

          客户为中心的产品管理工具

          专业的软件研发项目管理工具

          简单易用的团队知识库管理

          可量化的研发效能度量工具

          测试用例维护与计划执行

          以团队为中心的协作沟通

          研发工作流自动化工具

          账号认证与安全管理工具

          Why PingCode
          为什么选择 PingCode ?

          6000+企业信赖之选,为研发团队降本增效

        • 行业解决方案
          先进制造(即将上线)
        • 解决方案1
        • 解决方案2
  • Jira替代方案

25人以下免费

目录

ConcurrentHashMap弱一致的迭代器是什么原理

ConcurrentHashMap弱一致的迭代器的原理是:ConcurrentHashMap 的弱一致性迭代器是基于快照迭代器实现的。在 ConcurrentHashMap 中,每个 Segment 维护了一个元素计数器 modCount 和一个修改次数计数器 count。

一、ConcurrentHashMap弱一致的迭代器是什么原理

ConcurrentHashMap 的弱一致性迭代器是基于快照迭代器实现的。在 ConcurrentHashMap 中,每个 Segment(即 ConcurrentHashMap 中每个 key-value 对所在的 segment)维护了一个元素计数器 modCount 和一个修改次数计数器 count。当 segment 发生修改操作时,count 计数器会自增,同时 modCount 计数器也会自增。当使用迭代器对 ConcurrentHashMap 进行遍历时,迭代器会在每次访问之前,记录当前 modCount 值,若在迭代过程中 ConcurrentHashMap 发生了修改操作,modCount 值会发生变化,迭代器会发现这个变化,会抛出 ConcurrentModificationException 异常。

因此,ConcurrentHashMap 迭代器的弱一致性体现在它不保证遍历结果即使在迭代过程中 ConcurrentHashMap 发生了修改,但它保证迭代器不会因为在遍历时 ConcurrentHashMap 包含了新元素而抛出异常。同时这种设计也能保证在大多数情况下,迭代器访问的元素都不会太过陈旧,可以满足迭代器的绝大部分使用场景。

二、ConcurrentHashMap简介

java.util.concurrent.ConcurrentHashMap 属于 JUC 包下的一个集合类,可以实现线程安全。它由多个 Segment 组合而成。Segment 本身就相当于一个 HashMap 对象。同 HashMap 一样,Segment 包含一个 HashEntry 数组,数组中的每一个 HashEntry 既是一个键值对,也是一个链表的头节点。

ConcurrentHashMap的put方法流程(JDK1.7):

   public V put(K key, V value) {
       //key,value不能为null
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
       //通过key进行哈希到对应segment位置
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        //通过位置j获取当前的对应segment起始位置
        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);
    }

  #内部类Segment下的put方法
        final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            //尝试性加锁
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                //当前segment下的table
                HashEntry<K,V>[] tab = table;
                //通过key的哈希值进行哈希找到对应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) {
                            //put方法处理:将新value替换oldvalue
                                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进行扩容,而不是对整个ConcurrentHashMap进行扩容
      private void rehash(HashEntry<K,V> node) {
          //在segment下的table
            HashEntry<K,V>[] oldTable = table;
            int oldCapacity = oldTable.length;
            //按照原大小2倍关系进行扩容 
            int newCapacity = oldCapacity << 1;
            threshold = (int)(newCapacity * loadFactor);
            HashEntry<K,V>[] newTable =(HashEntry<K,V>[]) new HashEntry[newCapacity];
            int sizeMask = newCapacity - 1;
            //将原有table上的所有hashentry节点进行重新哈希到新table上
            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
                        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];
                            newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                        }
                    }
                }
            }
            int nodeIndex = node.hash & sizeMask; // add the new node
            node.setNext(newTable[nodeIndex]);
            newTable[nodeIndex] = node;
            table = newTable;
        }

三、ConcurrentHashMap和HashMap的区别

ConcurrentHashMap和HashMap的区别如下:

  1. HashMap是非线程安全的;而ConcurrentHashMap是线程安全的。
  2. HashMap的key和value均可以为null;而ConcurrentHashMap的key和value均不可以为null。
  3. HashMap是通过给整张散列表加锁的方式来保证线程安全,这种方式保证了线程安全,但是并发执行效率低下;ConcurrentHashMap在JDK1.8之前,采用分段锁机制来保证线程安全的,这种方式可以在保证线程安全的同时,一定程度上提高并发执行效率(当多线程并发访问不同的segment时,多线程就是完全并发的,并发执行效率会提高)。但从JDK1.8开始,ConcurrentHashMap数据结构与1.8中的HashMap保持一致,均为数组+链表+红黑树,是通过乐观锁+Synchronized来保证线程安全的。当多线程并发向同一个散列桶添加元素时。若散列桶为空,此时触发乐观锁机制,线程会获取到桶中的版本号,在添加节点之前,判断线程中获取的版本号与桶中实际存在的版本号是否一致,若一致,则添加成功,若不一致,则让线程自旋。

延伸阅读1:JDK1.7 与 JDK1.8 中 ConcurrentHashMap 的区别

  • 数据结构:取消了 Segment 分段锁的数据结构,取而代之的是数组+链表+红黑树的结构。
  • 保证线程安全机制:JDK1.7 采用Segment 的分段锁机制实现线程安全,其中 Segment 继承自 ReentrantLock 。JDK1.8采用CAS+synchronized保证线程安全。
  • 锁的粒度:JDK1.7 是对需要进行数据操作的 Segment 加锁,JDK1.8调整为对每个数组元素加锁(Node)。
  • 链表转化为红黑树:定位节点的 hash 算法简化会带来弊端,hash冲突加剧,因此在链表节点数量大于 8(且数据总量大于等于 64)时,会将链表转化为红黑树进行存储。
  • 查询时间复杂度:从JDK1.7的遍历链表O(n), JDK1.8 变成遍历红黑树O(logN)。
相关文章