您好!欢迎来到爱源码

爱源码

热门搜索: 抖音快手短视频下载   

并发编程的ConcurrentHashMap源代码解读-1.7 <源码交易平台>

  • 时间:2022-07-06 02:42 编辑: 来源: 阅读:322
  • 扫一扫,手机访问
摘要:并发编程的ConcurrentHashMap源代码解读-1.7 <源码交易平台>
在上一篇文章《并发编程的并发哈希表源代码解释-1.8》中,我们详细解释了JDK 1.8中并发哈希表的源代码。 既然两者都提到了JDK 1.8的ConcurrentHashMap,那就不得不提一下JDK 1.7的ConcurrentHashMap,两者的实现大相径庭。 让我们在这篇文章中探索一下吧!本文主要包括以下几个部分:前言、基本原理、基本组成、put展开、getsize总结、1。前言,由于ConcurrentHashMap在JDK 1.7 & 1.8中有着完全不同的实现,所以有必要描述一下ConcurrentHashMap在JDK 1.7中的实现(基于本文的JDK版本是1.7.0_80)。 在进入正文之前,像往常一样,问几个问题:Q1:你为什么不用HashTable?HashTable采用在所有可能的线程安全方法中添加同步锁的方法来实现线程安全。 锁粒太粗,效率低。 Q2:Q2:并发哈希表是如何实现线程安全的?ConcurrentHashMap在内部维护一个段[],当该段从ReentrantLock继承时,put,先定位特定的段,然后获取段锁。只有成功获得锁的线程才能插入数据。 从而通过使用ReentrantLock实现线程安全。 Q3:Q3:concurrent hashmap如何降低锁的粒度?维护多个细分市场。插入数据时,仅锁定相应的段,而不是所有段。 Q4:Q4:concurrent hashmap扩展时支持并发读写吗?扩展时,位于同一段的写线程将被阻塞,直到扩展完成。 读取线程不受影响,始终可以通过UNSAFE.getObjectVolatile获取最新值,通过volatile HashEntry保证扩展数组的可见性。 2.基本原理持有一段< K,V >[],用于对锁进行分段,从而降低锁的粒度,提高并发性能。 1.1段继承自ReentrantLock,保证了它的锁特性。 1.2段还保存易失性散列条目 1.3段& ltk,V & gt[]将在调用构造函数时被初始化,并且只有散列条目 Put先进行哈希扰动操作,然后定位具体的段,再根据情况决定是初始化段还是请求锁。 2.1哈希扰动(目标是充分混合哈希特性并减少冲突) 2.2计算索引得到段[i],如果还没有初始化,先初始化段[i]。 2.3如果您尝试获取段[i]的锁,并且未能获取锁,您将被阻止,直到您成功获取锁。 2.4定位散列条目 2.5解锁 在获取时,也是先执行哈希扰动操作,然后定位特定的段,再定位特定的哈希条目 3.1取特定的散列条目 如果中途数据没有变化,则认为数据是正确的;如果有变化,会在锁定的情况下重新计数。 [图片上传...(0073 CJ 6 X6 ly gkj 896 whqvj 3020020 dfw . jpg-ef6b 90-1608789320783-0)]从上面的过程中我们可以清楚的看到ConcurrentHashMap中的数据结构是segment < K,V & gt[]+HashEntry & lt;k,V & gt[]并发哈希表结构图。放入进程。png3。基本组件属性的默认初始化容量static final int default _ initial _ capacity = 16;负载系数静态最终浮动default _ load _ factor = 0.75f默认并发级别static final int default _ concurrency _ level = 16;存储哈希条目;1 ?64 : 1;哈希条目<用于实际存储数据 还有一点需要注意的是,当你调用put方法时,实际上是在段中调用put方法。 关于ReentrantLock如何实现锁功能,可以看另一篇文章:AQS探索HashEntry链表静态最终类Hashentry 我们结合源代码来看一下各个部分的实现,重点是如何保证每一步的线程安全。 看构造方法public concurrency hash map (int初始容量,float load factor,int并发级别){if(!(负载系数& gt0) ||初始容量& lt0 ||并发级别& lt= 0)抛出新的IllegalArgumentException();if(concurrency level & gt;MAX _ SEGMENTS)concurrency level = MAX _ SEGMENTS;//查找2的幂大小的最佳匹配参数int sshift = 0;int ssize = 1;这里得到的ssize永远是2的n次方,因为它从1向左移动一位而(ssize:MAXIMUM _ CAPACITY)initial CAPACITY = MAXIMUM _ CAPACITY;看int c = initialCapacity/ssize是否整除;如果(c * ssize < InitialCapacity)取较大的++c,则无法整除;int cap = MIN _ SEGMENT _ TABLE _ CAPACITY;计算每个HashEntry[]的初始长度while(cap < c)cap & lt;& lt= 1;//创建segments和segments[0]初始segment < K,V & gt[0]作为其他初始段的模板(可以通过s0得到cap和load因子)段< K,V & gts0 =新段& ltk,V & gt(loadFactor,(int)(cap * loadFactor),(HashEntry & ltk,V & gt[])新散列条目[cap]);起始程序段 Entry public v put (k key,v value) {segment < K,V & gts;if (value == null)抛出新的NullPointerException();做哈希扰动,充分混合哈希特征,减少冲突int hash = hash(key);计算段索引int j =(hash >:& gt;& gtsegment shift)& amp;分段掩码;如果(s = (segment,则选择指定的段 初始化段[i]私有段 2)升级段< K,V >[i]的值,直到成功或分段 调用segment.put完成最终v put的赋值(k key,int hash,v value,boolean only lyifabsent){尝试无阻塞获取锁,成功返回null。如果在成功获取锁之前未能获得锁、旋转或阻塞。当前段[i]在此处被锁定,因此当多个线程同时放入同一个段[i]时,只有获得锁的线程才能成功设置值,而未能获得锁的线程将旋转或阻塞,直到锁哈希条目 当多个线程同时放入同一个段[i]时,只有获得锁的线程才能成功设置值,没有获得锁的线程将会自旋或阻塞,直到成功获得锁 这里升级链表采用的是头插入方式,和1.8版本的尾插入方式不同。 entryAt静态最终& ltk,V & gt哈希条目& ltk,V & gtentryAt(hash entry & lt;k,V & gt[] tab,int i) { return (tab == null)?null:(hash entry & lt;k,V & gt)UNSAFE.getObjectVolatile (tab,((long)I & lt;& ltt shift)+t base);}非volatile定义的变量也可以有volatile读取的语义,即可以读取最新的值。 setEntryAt静态最终& ltk,V & gtvoid setEntryAt(HashEntry & lt;k,V & gt[] tab,int i,HashEntry & ltk,V & gte) { UNSAFE.putOrderedObject(tab,((long)I & lt;& ltTSHIFT) + TBASE,e);}非volatile定义的变量也可以有volatile写的语义,即可以禁止写操作的指令的重新排序,但不保证内存的可见性。因此,应将其与UNSAFE.getObjectVolatile结合使用,以获取最新值。 5.扩展ConcurrentHashMap正在升级HashEntry < K,V & gt在[]之前,我们需要检查容量扩展。如果当前段元素的数量达到操作阈值,并且数组长度小于最大值,则我们需要扩展容量。 扩展主要做两件事:创建一个两倍于原始数组大小的数组。 将旧数组的元素转移到新数组。 在传递数组的过程中,将后续节点索引保持不变的节点循环找到扩展后插入到相应的槽中,实现链表的重用。 然后分别转移剩余的节点。 这种解决方案可以最大程度地重用现有的链表。最好的情况是,链表的头在未来保持不变,这样整个链表都可以重用。当然,在最坏的情况下,如果最后一个节点保持不变,那么除了最后一个节点之外的所有链表都需要重新构建。 有点类似于1.8 ConcurrentHashMap中的高位和低位。有兴趣的可以看看之前的文章关于高低位的细节。并发编程的ConcurrentHashMap源代码解读——1.8 Private void rehash(hash entry < K,V & gtnode){ hash entry & lt;k,V & gt[]old table = table;int old capacity = old table . length;int newCapacity = oldCapacity & lt& lt1;新阈值= (int)(新容量*负载系数);初始化数组散列条目 6.get public V get(Object key){ Segment & lt;k,V & gts;//手动集成访问方法以减少开销HashEntry & ltk,V & gt[]选项卡;hash int h = hash(key);long u =(((h & gt;& gt& gtsegment shift)& amp;分段掩码)& lt& ltSSHIFT)+s base;如果((s = (segment ),请使用UNSAFE.getObjectVolatile获取最新的段[u] 哈希条目& ltk,V & gt[i]同样地 7.size如果我们想统计ConcurrentHashMap中元素的个数,我们能做什么?自然的想法是统计每一段的计数,然后累加。 但是这样可以吗?显然是做不到的。试想一下,如果在统计的过程中计数值发生了变化。我该怎么办? 那么最安全的办法就是把所有的段都锁起来,这样其他线程就不能更改里面的值了,也就是可以放心的计数了。 这是安全的,但是效率很低,那么ConcurrentHashMap是如何处理的呢?public int size() { //尝试几次以获得准确的计数。如果由于//表中连续的异步变化而导致失败,则求助于锁定。最后一段& ltk,V & gt[]segments = this . segments;返回的最终大小int size大小超过32位布尔溢出;//如果size溢出s32bits mod count和long sum,则为true//modCounts之和,最后统计的mod counts之和,long last = 0L// previous sum不锁定的尝试次数,从-1开始,最大值等于2。总共有三个整数=-1;//第一次迭代不是重试尝试{无限循环统计for(;;){三次后锁定if(retries++ = = retries _ before _ lock){ for(int j = 0;j & ltsegments .长度;+j)锁定所有段,这将强制创建所有段。创建和锁定段可以防止其他线程调用put和其他操作,如ensureSegment(j)。lock();//强制创建}重置sum sum = 0L重置大小size = 0;溢出=假;遍历段数组for(int j = 0;j & ltsegments .长度;++j) {Secure。GetObject Volatile获取segment segment < K,V >的最新值;seg = segmentAt(segments,j);如果(seg!= null){累积modCount sum+= seg . modCount;int c = seg.count累积计数,如果(c 如果两次统计之间计数没有变化(判断变化的依据是两次循环的modCount相同),则认为结果正确,否则继续尝试。 当最大尝试次数达到3时,将对每个数据段进行锁统计。 8.摘要在本文中,我们对JDK 1.7 concur hashmap的源代码进行了分析,并用以下几个问题对上述内容进行了总结。 1.并发HashMap如何保证线程安全?从读取(get)的层面上,使用Unsafe.getObjectVolatile来保证最新的段& HashEntry & ltk,V & gt[i],保证写线程对读线程的可见性,从而保证线程安全。从write(put)的层面来说,ReentrantLock是用来保证一次只有一个线程向Segment[i]插入数据,从而保证线程安全。 从rehash的层面来说,rehash只有在put的时候才会被调用,使用ReentrantLock可以保证线程之间的互斥,从而保证线程安全。 从计数(大小)方面来说,放的时候是自增的,计数的时候是先尝试计数不加锁,计数过程中结果发生变化的时候采用ReentrantLock,保证计数过程中其他线程不能更改数据,实现线程安全。 2.什么情况下并发Hashmap会触发扩展?当当前段[i]中的元素数量超过阈值(加载因子*初始数量)并且数组长度小于最大长度时,3。可以支持并发扩展吗?不会,扩展时会定位到同一个段,准备进行数据更改的线程会被阻塞。 4.扩容时能支持并发读写吗?扩容时支持并发读取。 容量扩展期间不支持并发写入(同一段),此时将阻止其他线程向该段写入数据。 ConcurrentHashMap作为并发编程的一个热点,无论是在工作中还是在面试中都出现的频率很高。 相信通过对源代码的解读,大家都能有所收获。 当然,由于水平有限,文章难免有疏漏。请批评指正。 我们下一篇文章再见.....参考java并发编程的艺术。


  • 全部评论(0)
资讯详情页最新发布上方横幅
最新发布的资讯信息
【域名/主机/服务器|】qq邮箱提醒在哪里打开(2024-06-04 18:58)
【技术支持|常见问题】1556原创ng8文章搜索页面不齐(2024-05-01 14:43)
【技术支持|常见问题】1502企业站群-多域名跳转-多模板切换(2024-04-09 12:19)
【技术支持|常见问题】1126完美滑屏版视频只能显示10个(2024-03-29 13:37)
【技术支持|常见问题】响应式自适应代码(2024-03-24 14:23)
【技术支持|常见问题】1126完美滑屏版百度未授权使用地图api怎么办(2024-03-15 07:21)
【技术支持|常见问题】如何集成阿里通信短信接口(2024-02-19 21:48)
【技术支持|常见问题】算命网微信支付宝产品名称年份在哪修改?风水姻缘合婚配对_公司起名占卜八字算命算财运查吉凶源码(2024-01-07 12:27)
【域名/主机/服务器|】帝国CMS安装(2023-08-20 11:31)
【技术支持|常见问题】通过HTTPs测试Mozilla DNS {免费源码}(2022-11-04 10:37)

联系我们
Q Q:375457086
Q Q:526665408
电话:0755-84666665
微信:15999668636
联系客服
企业客服1 企业客服2 联系客服
86-755-84666665
手机版
手机版
扫一扫进手机版
返回顶部