赣州市建设局网站,网站设计制作规范,做会员卡网站,简单的设计软件文章目录1. 并发安全的演进#xff1a;从分段锁到CASJDK 1.7的分段锁设计JDK 1.8的革命性改进2. 核心技术机制深度剖析2.1 CAS#xff08;Compare And Swap#xff09;操作2.2 synchronized精细化同步2.3 volatile变量的魔法3. 关键操作的线程安全实现3.1 put操作—如何安全…文章目录1. 并发安全的演进从分段锁到CASJDK 1.7的分段锁设计JDK 1.8的革命性改进2. 核心技术机制深度剖析2.1 CASCompare And Swap操作2.2 synchronized精细化同步2.3 volatile变量的魔法3. 关键操作的线程安全实现3.1 put操作—如何安全地插入数据3.2 get操作—无锁读取的高效设计3.3 size操作—智能的统计策略4. 实战场景与最佳实践4.1 缓存场景下的应用4.2 计数器场景的优化4.3 需要注意的陷阱5. 性能对比与优化建议5.1 与其它并发容器的对比5.2 优化建议6. 总结与展望参考文章大家好我是你们的船长科威舟今天给大家分享一下ConcurrentHashMap如何优雅地实现线程安全在多线程编程的世界里数据竞争和线程安全是每个程序员必须面对的挑战。今天我们要揭秘的是Java并发包中一个真正的明星—ConcurrentHashMap它如何在不牺牲性能的情况下保证线程安全。在开始深入探讨之前先让我们想象一个场景一个大型超市我们的程序有多个收银台线程顾客数据需要快速结账处理。如果所有顾客都排在一个收银台效率肯定低下—这就是HashTable的做法如果完全不管排队秩序让顾客随意争抢—这是HashMap的线程不安全做法。而ConcurrentHashMap则像是一个智能的超市管理系统它既保证了秩序又最大化提高了效率。1. 并发安全的演进从分段锁到CASJDK 1.7的分段锁设计在JDK 1.7中ConcurrentHashMap采用了一种称为分段锁的创新设计。它将整个哈希表分成多个段Segment每个段都是一个独立的哈希表拥有自己的锁。这相当于将一个大超市划分成多个部门食品区、服装区、家电区每个部门有自己独立的收银台。不同部门的顾客可以同时结账只有同一部门的顾客才需要排队。// JDK 1.7中的Segment类就是一个独立的哈希表加锁staticfinalclassSegmentK,VextendsReentrantLockimplementsSerializable{transientvolatileHashEntryK,V[]table;transientintcount;// ...}具体put操作如下publicVput(Kkey,Vvalue){inthashhash(key);intsegmentIndexgetSegmentIndex(hash);// 定位到哪个Segmentreturnsegments[segmentIndex].put(key,hash,value,false);}这种设计显著减少了锁的竞争默认情况下有16个段意味着理论上允许16个线程并发写入相比HashTable的全表锁性能提升巨大。JDK 1.8的革命性改进JDK 1.8对ConcurrentHashMap进行了彻底重写放弃了分段锁采用了更为精细的CAS synchronized方案。这好比将超市的收银系统进一步优化—现在每个商品货架都有了自己的微型管理系统只有在真正需要时才进行同步。// JDK 1.8的putVal方法关键部分finalVputVal(Kkey,Vvalue,booleanonlyIfAbsent){// ...if((ftabAt(tab,i(n-1)hash))null){// 如果位置为空使用CAS无锁插入if(casTabAt(tab,i,null,newNodeK,V(hash,key,value,null)))break;}else{synchronized(f){// 只锁住当前桶的第一个节点// 具体的插入逻辑}}// ...}2. 核心技术机制深度剖析2.1 CASCompare And Swap操作CAS是一种乐观锁机制它包含三个操作数内存位置V、预期原值A和新值B。当且仅当V的值等于A时CAS才会通过原子方式用B更新V的值否则什么都不做。在ConcurrentHashMap中大量使用了CAS操作// 获取tab数组的第i个节点staticfinalK,VNodeK,VtabAt(NodeK,V[]tab,inti){return(NodeK,V)U.getObjectVolatile(tab,((long)iASHIFT)ABASE);}// 使用CAS算法设置i位置上的节点staticfinalK,VbooleancasTabAt(NodeK,V[]tab,inti,NodeK,Vc,NodeK,Vv){returnU.compareAndSwapObject(tab,((long)iASHIFT)ABASE,c,v);}这就像去超市购物时看到心仪商品标签上写着限购1件你拿了一件去结账。如果收银系统发现库存还有就允许购买如果已经被别人买走你就需要重新选择。2.2 synchronized精细化同步虽然CAS很高效但并非适用于所有场景。JDK 1.8在发生哈希冲突时使用synchronized对单个桶桶的第一个节点进行加锁。锁粒度从分段缩小到单个桶这是性能提升的关键。现代JDK对synchronized做了大量优化性能损失已大大降低。2.3 volatile变量的魔法ConcurrentHashMap中大量使用volatile关键字来保证内存可见性staticclassNodeK,VimplementsMap.EntryK,V{finalinthash;finalKkey;volatileVvalue;// 使用volatile保证可见性volatileNodeK,Vnext;// ...}volatile相当于给变量加了一个广播系统—任何线程的修改都会立即通知到所有其他线程保证了数据的实时可见性。3. 关键操作的线程安全实现3.1 put操作—如何安全地插入数据put操作是ConcurrentHashMap最复杂的方法其线程安全通过以下步骤保证计算哈希值根据key计算hash确定桶的位置表未初始化则先初始化使用sizeCtl变量控制保证只初始化一次桶为空则CAS插入如果定位到的桶为空直接CAS插入新节点桶不为空则synchronized加锁锁住桶的第一个节点处理链表或红黑树插入判断是否需要扩容在达到阈值时进行线程安全的扩容整个过程就像去图书馆还书先找到正确的书架哈希定位如果书架空着直接放书CAS插入如果书架上已有书则需要管理员的协助synchronized加锁来整理。3.2 get操作—无锁读取的高效设计get操作是完全无锁的这也是ConcurrentHashMap在高并发读场景下性能优异的关键。publicVget(Objectkey){NodeK,V[]tab;NodeK,Ve,p;intn,eh;Kek;inthspread(key.hashCode());if((tabtable)!null(ntab.length)0(etabAt(tab,(n-1)h))!null){// 无锁遍历链表或红黑树if((ehe.hash)h){if((eke.key)key||(ek!nullkey.equals(ek)))returne.val;}// ... 其他情况处理}returnnull;}这就像超市的顾客可以随意浏览商品读取数据而不用打扰收银系统只有当他们要实际购买修改数据时才需要排队。3.3 size操作—智能的统计策略size操作的实现体现了ConcurrentHashMap在精确性和性能之间的平衡先尝试无锁统计遍历所有节点计数如果检测到有并发修改则重试如果重试多次仍然有修改则转为加锁统计这种设计类似于超市人流量统计先通过摄像头大致估算无锁统计如果人流量变化太频繁再派人实际点数加锁统计。4. 实战场景与最佳实践4.1 缓存场景下的应用ConcurrentHashMap是实现高性能缓存的理想选择。例如我们可以实现一个带过期时间的缓存publicclassExpiringCacheK,V{privatefinalConcurrentHashMapK,CacheValueVcachenewConcurrentHashMap();publicvoidput(Kkey,Vvalue,longttl){CacheValueVcacheValuenewCacheValue(value,System.currentTimeMillis()ttl);cache.put(key,cacheValue);}publicVget(Kkey){CacheValueVcacheValuecache.get(key);if(cacheValuenull||cacheValue.isExpired()){cache.remove(key);returnnull;}returncacheValue.getValue();}privatestaticclassCacheValueV{privatefinalVvalue;privatefinallongexpiryTime;// 构造方法和getter省略booleanisExpired(){returnSystem.currentTimeMillis()expiryTime;}}}4.2 计数器场景的优化对于高并发计数器ConcurrentHashMap提供了更好的解决方案// 线程安全的计数器ConcurrentHashMapString,LongcounternewConcurrentHashMap();// 原子性增加计数publiclongincrement(Stringkey){returncounter.compute(key,(k,v)-vnull?1L:v1L);}// 获取所有计数和publiclongtotalCount(){returncounter.reduceValuesToLong(1,v-v,0L,Long::sum);}4.3 需要注意的陷阱虽然ConcurrentHashMap很强大但使用时仍需注意复合操作不是原子性的多个连续操作需要外部同步// 不安全的复合操作if(!map.containsKey(key)){map.put(key,value);// 这两个操作之间可能有其他线程修改}// 安全的原子操作map.putIfAbsent(key,value);迭代器的弱一致性ConcurrentHashMap的迭代器反映的是创建时的状态不抛出ConcurrentModificationExceptionnull值禁止与HashMap不同ConcurrentHashMap不允许key和value为null避免在并发环境中的歧义5. 性能对比与优化建议5.1 与其它并发容器的对比容器锁粒度读性能写性能适用场景Hashtable全表锁差差不推荐使用Collections.synchronizedMap全表锁差差低并发场景ConcurrentHashMap(JDK 1.7)段锁良好良好中等并发ConcurrentHashMap(JDK 1.8)桶锁优秀优秀高并发5.2 优化建议合理设置初始容量避免频繁扩容根据预估数据量设置合适的初始大小并发级别设置在JDK 1.7中可以根据并发线程数设置合适的并发级别在JDK 1.8中这个参数主要是为了兼容性考虑键的哈希质量键对象的哈希码分布影响性能尽量避免哈希冲突6. 总结与展望ConcurrentHashMap是Java并发编程中的一颗明珠它通过精细的锁设计、CAS操作和无锁读技术在保证线程安全的同时提供了优异的性能。从JDK 1.7的分段锁到JDK 1.8的CASsynchronized体现了并发优化技术的演进趋势锁粒度越来越细无锁化范围越来越大。随着虚拟线程在Java 19中的引入ConcurrentHashMap可能会有新的优化方向比如更好地应对海量线程访问的场景。但它的核心思想—尽可能减少锁竞争最大化并发度—将一直是高并发编程的指导原则。正如ConcurrentHashMap的作者Doug Lea所说好的并发设计就像是精心设计的交通系统既要保证车辆有序通行又要避免不必要的等待。ConcurrentHashMap正是这一理念的完美体现。参考文章https://juejin.cn/post/7250037058684518459https://bbs.huaweicloud.com/blogs/451772https://www.cnblogs.com/i-xq/p/13073727.htmlhttps://blog.csdn.net/Cactus_Lrg/article/details/82781034https://blog.csdn.net/weixin/43207025/article/details/114855495https://blog.csdn.net/weixin/45187434/article/details/127374646https://blog.csdn.net/qq_43279073/article/details/97171662https://blog.csdn.net/chi_666/article/details/145955885https://blog.csdn.net/qq_38129621/article/details/147375839https://www.cnblogs.com/chougoushi/p/14498903.html本文旨在用通俗易懂的方式讲解ConcurrentHashMap的线程安全机制实际源码更为复杂建议读者结合JDK源码深入学习。如有错误欢迎指正更多技术干货欢迎关注微信公众号科威舟的AI笔记~【转载须知】转载请注明原文出处及作者信息