ConcurrentHashMap弱一致的迭代器是什么原理?
一、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)。
相关推荐HOT
更多>>
python .pyc .pyd .pyo文件的区别?
一、python .pyc .pyd .pyo文件的区别.pyc文件类型我们首先考虑.pyc文件类型,当你导入一个模块时,解释器会自动生成.pyc文件,这样会节省下次...详情>>
2023-10-14 19:43:23
trello怎么下载?
一、前往Trello官网您需要前往Trello 官网(https://trello.com/)。在该网站的首页上,您可以看到“Sign Up”和“Log In”两个选项。如果您已...详情>>
2023-10-14 15:59:51
为什么快速排序在最坏情况下仍然要比冒泡排序快?
一、快速排序在最坏情况下仍然要比冒泡排序快的原因1、数据交换次数少在快速排序的过程中,每一次分割都能将序列划分为两个子序列,并将序列中...详情>>
2023-10-14 15:07:25
用数组或链表实现栈各有什么特点?
一、用数组或链表实现栈各有什么特点使用数组实现栈的特点:1、随机访问数组是一段连续的内存空间,可以通过索引直接访问数组中的任意元素,因...详情>>
2023-10-14 12:23:59热门推荐
Kotlin对APP测试意味着什么?
沸为什么Java后端开发没有大规模采用 Kotlin?
热Python有哪些常用的标准库?
热哪些技术会决定前端开发者的未来发展?
新主流图片加载库所使用的预解码究竟干了什么?
Java中Vector和SynchronizedList的区别?
哪些python技能—封包解包与函数参数?
python .pyc .pyd .pyo文件的区别?
列表、元组、字典、集合的区别?
云下载和本地重新安装有什么区别?
Python内置函数有哪些?
CameraX 1.1 有哪些新的特性发布?
wiki怎么编辑页面?
有什么软件像trello?
技术干货






