千锋教育-做有情怀、有良心、有品质的职业教育机构

400-811-9990
手机站
千锋教育

千锋学习站 | 随时随地免费学

千锋教育

扫一扫进入千锋手机站

领取全套视频
千锋教育

关注千锋学习站小程序
随时随地免费学习课程

上海
  • 北京
  • 郑州
  • 武汉
  • 成都
  • 西安
  • 沈阳
  • 广州
  • 南京
  • 深圳
  • 大连
  • 青岛
  • 杭州
  • 重庆
当前位置:贵阳千锋IT培训  >  技术干货  >  ConcurrentHashMap弱一致的迭代器是什么原理?

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

来源:千锋教育
发布人:xqq
时间: 2023-10-14 14:00:37

一、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 s;        if (value == null)            throw new NullPointerException();       //通过key进行哈希到对应segment位置        int hash = hash(key);        int j = (hash >>> segmentShift) & segmentMask;        //通过位置j获取当前的对应segment起始位置        if ((s = (Segment)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 node = tryLock() ? null :                scanAndLockForPut(key, hash, value);            V oldValue;            try {                //当前segment下的table                HashEntry[] tab = table;                //通过key的哈希值进行哈希找到对应table位置                int index = (tab.length - 1) & hash;                HashEntry first = entryAt(tab, index);                for (HashEntry 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(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 node) {          //在segment下的table            HashEntry[] oldTable = table;            int oldCapacity = oldTable.length;            //按照原大小2倍关系进行扩容             int newCapacity = oldCapacity << 1;            threshold = (int)(newCapacity * loadFactor);            HashEntry[] newTable =(HashEntry[]) new HashEntry[newCapacity];            int sizeMask = newCapacity - 1;            //将原有table上的所有hashentry节点进行重新哈希到新table上            for (int i = 0; i < oldCapacity ; i++) {                HashEntry e = oldTable[i];                if (e != null) {                    HashEntry 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 lastRun = e;                        int lastIdx = idx;                        for (HashEntry 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 p = e; p != lastRun; p = p.next) {                            V v = p.value;                            int h = p.hash;                            int k = h & sizeMask;                            HashEntry n = newTable[k];                            newTable[k] = new HashEntry(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的区别如下:

HashMap是非线程安全的;而ConcurrentHashMap是线程安全的。HashMap的key和value均可以为null;而ConcurrentHashMap的key和value均不可以为null。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)。
声明:本站稿件版权均属千锋教育所有,未经许可不得擅自转载。

猜你喜欢LIKE

vector容器原理是什么?

2023-10-14

二叉树非递归遍历栈中存的是什么?

2023-10-14

做进度计划用什么软件?

2023-10-14

最新文章NEW

Kotlin对APP测试意味着什么?

2023-10-14

Python有哪些常用的标准库?

2023-10-14

哪些技术会决定前端开发者的未来发展?

2023-10-14

相关推荐HOT

更多>>

快速通道 更多>>

最新开班信息 更多>>

网友热搜 更多>>