避坑!2个真实案例,揭示ConcurrentHashMap也不是100%线程安全

ConcurrentHashMap的线程安全处理真的能应对所有的线程安全,数据一致性的需求场景吗?如果ConcurrentHashMap解决不了的并发业务场景,就要用Hashtable来实现吗? 因为可能同一个业务中的另一个方法共用此Map,但是只需要put方法做互斥,防止扩容出现线程不安全等,后面会重点复现该场景)。所以很多人一提到ConcurrentHashMap就说它是线程安全的Map集合,虽然也没毛病,因为大部分的先获取再修改的逻辑,也有线程安全的computeIfAbsent,putIfAbsent来实现,但其实还是太了解它的设计目的,因为它的线程安全范围真的太小了,想要依靠JDK自带的一个集合就可以解决实际应用中复杂多变并发的线程安全和高吞吐量的需求,是很不现实的。 阅读详情

在这里插入图片描述

大家好,我是哪吒。

你是否曾遇到过这样的情况:明明使用了ConcurrentHashMap替换了普通的HashMap,系统依然出现了数据错乱?或者引入了CopyOnWriteArrayList,却发现在某些情况下读取的数据仍然不一致?

在Java高并发编程的世界里,有一个危险的误区:认为只要使用了并发工具类库,线程安全问题就会自动解决。这就像是购买了防弹衣,却没意识到它并不能防御所有类型的攻击一样。事实上,即使是专为并发设计的工具类,也有其保护范围和局限性。

本文将揭开Java并发工具类的神秘面纱,带你深入了解那些隐藏在"线程安全"承诺背后的潜在风险,以及如何在实际开发中规避这些陷阱。通过真实案例的剖析,你将看到为什么"知其然"还必须"知其所以然",才能真正掌握高并发编程的精髓。

一、并发工具类库可能存在的线程安全问题

1、ConcurrentHashMap的复合操作非原子性问题

虽然ConcurrentHashMap的单个操作(如get、put)是线程安全的,但多个操作的组合并不保证原子性。这就像多人同时操作一个银行账户,虽然单笔存取款是安全的,但"查询余额并取款"的复合操作如果不加锁,可能导致余额计算错误。

2、CopyOnWriteArrayList的快照一致性问题

CopyOnWriteArrayList在修改时会创建整个数组的副本,保证了修改的安全性。但这种设计导致了两个问题:

  1. 迭代器只能看到创建时的数据快照,无法感知后续修改(弱一致性)
  2. 频繁修改大数组会导致严重的性能问题和内存压力

二、案例1:电商系统库存管理中的ConcurrentHashMap问题

1、问题场景

在一个高并发电商平台中,使用ConcurrentHashMap存储商品ID和库存数量,多个线程同时处理订单时检查并减少库存。

2、存在问题的代码

ConcurrentHashMap<String, Integer> inventory = new ConcurrentHashMap<>();
// 初始化商品"A001"的库存为10
inventory.put("A001", 10);

// 多个线程并发执行以下方法
public boolean processOrder(String productId, int quantity) {
    Integer currentStock = inventory.get(productId);  // 读取当前库存
    if (currentStock != null && currentStock >= quantity) {
        inventory.put(productId, currentStock - quantity);  // 更新库存
        return true;
    }
    return false;
}

3、问题分析

虽然get和put方法各自是线程安全的,但它们的组合操作不是原子的。当两个线程同时读取到库存为10,都判断有足够库存并同时执行扣减操作时,最终库存可能被错误地减少。

例如:

  • 线程A和B同时读取库存为10
  • 线程A计算10-3=7,准备更新
  • 线程B计算10-2=8,准备更新
  • 线程B先完成更新,库存变为8
  • 线程A后完成更新,库存变为7
  • 实际应该是10-3-2=5,但最终变成了7,丢失了线程B的操作

4、解决方案

使用ConcurrentHashMap提供的原子性复合操作方法:

public boolean processOrder(String productId, int quantity) {
    // computeIfPresent方法保证了"检查并更新"操作的原子性
    return inventory.computeIfPresent(productId, (key, currentStock) -> {
        if (currentStock >= quantity) {
            return currentStock - quantity;  // 有足够库存,返回新值
        }
        return currentStock;  // 库存不足,保持不变
    }) != null && inventory.get(productId) < inventory.get(productId) + quantity;
}

三、案例2:Spring事务管理中的ThreadLocal隐患

1、问题场景

在一个Spring Boot应用中,使用ThreadLocal存储用户上下文信息,并在事务方法中使用这些信息。

2、存在问题的代码

@Service
public class UserService {
    private static ThreadLocal<UserContext> userContextHolder = new ThreadLocal<>();
    
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public void updateUserProfile(UserProfile profile) {
        // 设置当前用户上下文
        UserContext context = new UserContext(profile.getUserId());
        userContextHolder.set(context);
        
        // 长时间操作,如调用外部服务
        callExternalService();
        
        // 使用上下文信息更新数据库
        userRepository.save(profile);
        
        // 清理上下文
        userContextHolder.remove();
    }
}

3、问题分析

当方法执行过程中抛出异常时,userContextHolder.remove()语句可能不被执行,导致ThreadLocal中的数据没有被清理。由于Web服务器通常使用线程池,同一个线程被重用时会携带之前请求的上下文信息,造成数据混乱。这就像办公室的共享笔记本,如果有人使用后忘记撕掉自己的笔记,下一个人可能会看到或者基于错误的信息工作。

4、解决方案

使用try-finally结构确保ThreadLocal清理,或者利用Spring的事务同步机制:

@Service
public class UserService {
    private static ThreadLocal<UserContext> userContextHolder = new ThreadLocal<>();
    
    @Autowired
    private UserRepository userRepository;
    
    @Transactional
    public void updateUserProfile(UserProfile profile) {
        try {
            // 设置当前用户上下文
            UserContext context = new UserContext(profile.getUserId());
            userContextHolder.set(context);
            
            // 注册事务同步回调,确保在事务完成后清理
            TransactionSynchronizationManager.registerSynchronization(
                new TransactionSynchronizationAdapter() {
                    @Override
                    public void afterCompletion(int status) {
                        userContextHolder.remove();  // 事务完成后清理
                    }
                }
            );
            
            // 长时间操作
            callExternalService();
            
            // 使用上下文更新数据库
            userRepository.save(profile);
        } catch (Exception e) {
            userContextHolder.remove();  // 异常情况下也清理
            throw e;  // 重新抛出异常
        }
    }
}

在这里插入图片描述

四、使用并发工具类的关键原则

1、正确理解线程安全的边界

并发工具类提供的线程安全保障仅限于单个操作,而非操作的组合。就像我们在电商库存案例中看到的,get和put单独是安全的,但组合使用时可能导致数据不一致。复合操作必须采取额外的同步措施或使用专门的原子性API。

2、优先使用原子性API

现代并发工具类通常提供了更高级的原子操作方法,如ConcurrentHashMap的compute、computeIfPresent、merge等。这些方法在内部实现了乐观锁机制,既保证了操作的原子性,又维持了较高的性能。在库存管理案例中,使用computeIfPresent方法就有效解决了"检查-更新"的竞态条件问题。

3、资源清理的防御性编程

对于ThreadLocal等需要显式清理的资源,必须采用防御性编程策略,始终使用try-finally结构确保清理代码在所有情况下都能执行。如Spring事务管理案例所示,遗漏清理步骤可能导致线程池环境中的数据污染,引发难以排查的间歇性问题。

4、了解并发工具的实现原理

不同并发工具适用于不同场景,例如CopyOnWriteArrayList的"写时复制"策略在读多写少场景下表现优异,但频繁修改大型集合会导致严重的性能问题和内存压力。选择合适的工具必须基于对其内部机制的理解和应用场景的分析。

5、正确处理事务边界

在使用Spring等框架的事务管理时,需特别注意事务提交时机与线程状态的关系。如案例所示,通过TransactionSynchronizationManager注册回调,可以确保资源在事务完成后得到适当清理,避免因异常导致的资源泄漏。

这五项原则不仅是技术细节,更是并发编程思维的体现。真正的线程安全不仅仅依赖于工具的选择,更取决于开发者对并发模型的理解深度和应用能力。通过正确把握并发工具的能力边界,合理设计系统架构,我们才能在复杂多变的高并发环境中构建出真正稳定可靠的应用系统。

五、总结

并发编程是Java开发中的一项核心技能,而对并发工具类的正确理解和使用则是这项技能的关键所在。

本文通过剖析ConcurrentHashMap的非原子性复合操作风险和ThreadLocal的资源泄露问题,揭示了仅仅依赖并发工具类无法保证线程安全的本质原因。真正的线程安全需要遵循正确理解安全边界、优先使用原子性API、采用防御性编程、深入了解工具原理以及谨慎处理事务边界这五大核心原则。

在高并发系统设计中,除了选择合适的工具,更重要的是建立系统化的并发思维模型,才能在复杂多变的并发环境中构建真正稳定可靠的应用系统。只有将并发工具类的使用与深刻的并发理论理解结合起来,才能真正掌握高并发编程的精髓,让你的系统在并发浪潮中屹立不倒。

六、国内直接使用最新GPT-4.5、满血ChatGPT4o、o1、o3-mini-high、Claude 3.7 Sonnet、满血DeepSeek R1、Grok 3

✅️谷歌浏览器直接访问

Claude使用地址:https://claude.nezhagpt.cloud

在这里插入图片描述

✅️ChatGPT使用地址:www.nezhasoft.cloud

在这里插入图片描述

1、纯原版ChatGPT、Claude

✅️官网原生页面

✅️真实Team会员账号

2、技术支持

✔️支持满血GPT-4o、o1、o3-mini-high、Claude 3.7 Sonnent、满血DeepSeek R1

✔️无需魔法

✔️个人独享

✔️Open Ai官网ChatGPT Plus账号,20美元/月,算上其他成本,最低160元/月,我们直接99元/月,并且质保30天,还有29元的周体验卡

3、支持所有GPTs + 自定义插件

支持ChatGPT所有插件,可创建自己的ChatGPT插件,使用朋友分享的自定义插件。

例如最强编程插件Code Copilot、AI绘画插件DALL-E、论文专属Consensus。

在这里插入图片描述

Java线程安全本质:JMM三大特性与实战避坑指南 线程安全是Java并发编程的基石概念,其核心在于理解Java内存模型(JMM)对原子性、可见性、有序性的底层约束。原子性保障操作不可分割,避免i++等复合操作的丢失更新;可见性确保线程间状态变更及时同步,依赖volatile或锁机制实现;有序性防止指令重排破坏逻辑时序,需借助happens-before规则校准。这些原理共同支撑着synchronized、ReentrantLock、AtomicInteger、ConcurrentHashMap等关键工具的选型与正确使用。在高并发Web服务、订单系统、配置中 阅读详情

相关推荐

用AI助手Cursor轻松自动生成流程图

通过 Cursor + Mermaid,我们可以快速、准确地自动生成流程图,上面演示的例子部分流程图可能比较丑,我们可以通过流程图软件导入Mermaid代码,然后手动进行微调就可以了,如果你对美观要求不是特别多,可以直接用,这将极大地提升代码可视化和架构设计的效率。

呱牛 Just do IT 1936

spring ioc原理

原理:一般指的是代码的实现 以及涉及到的技术 一:以xml的形式来说 1.主要涉及到xml解析技术 以及 反射机制 还有实例存储的数据结构(容器) 1)xml解析技术: 包括dom4j 以及jdom支持xpath… 功能更强大 推荐使用 2)反射机制: 首先得获得类的全路径,该类还得有构造器,如果你要是写了有参构造器的话,必须得显示写无参构造器。默认情况下,该类是自带无参构造器。 ...

王林冲的博客 378

微信内置浏览器抓包全攻略:从ADB配置到Chrome调试(附常见问题解决)

本文提供了一套完整的微信内置浏览器抓包与调试解决方案。从ADB工具的深度配置与稳定性连接开始,详细讲解了如何开启X5内核调试开关,并利用Chrome DevTools建立远程连接。文章重点剖析了Network面板进行网络请求抓取、性能分析和问题定位的实战技巧,并附带了设备连接失败、无网络请求等常见问题的排查指南,帮助开发者高效解决微信H5页面调试难题。

gitlab7runner的博客 541

Concurrenthashmap的实现原理分析

1.    HashMap概述 HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同)。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。   值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Col

不行就改名 3万+

dify1.3.1应用服务与数据服务分离,附件及插件包迁移至minio,实现离线部署,保证插件正常运行

dify1.3.1应用服务与数据服务分离,附件及插件包迁移至minio,实现离线部署,保证插件正常运行

Time_1023的博客 1872

运放+MOS恒流源电路仿真分析

利用运放设计一个恒流源

HW-BASE的博客 4081

springboot源码学习(一)- ConcurrentReferenceHashMap深度解读

一、前言 最近一直在阅读springboot的启动源码,它通过加载jar包中的META-INF/spring.factories文件,通过反射实例化其实现类,实现自动装配。其中有一个数据结构被设计来缓存从spring.factorys中读取的接口及其实现类,即:ConcurrentReferenceHashMap,拿出来给大家分享一下。 二、主要功能 支持高并发,基于分段锁实现,性能优于hashTable 和ConcurrentHashMap设计约束一致,但是支持key和value都为null 默认引用方

笑不语的博客 3812

021、反激变换器的软启动设计

本文分享了反激变换器软启动设计的实践经验。作者通过一次上电炸机事故引出软启动的重要性,分析了启动瞬间电流冲击和环路饱和问题。介绍了三种软启动方案:效果差的RC电路、推荐使用的数字芯片方案以及分立元件实现方法,重点指出数字方案的一致性优势。文章还探讨了软启动与过流保护的配合问题,提供了实测波形、温度影响等调试要点,并总结出软启动时间选择、批量生产一致性等实用建议。最后强调软启动是电源可靠性的关键设计,不应为节省成本而忽略。全文包含多个工程实践细节,对电源设计人员具有重要参考价值。(149字)

Jack010169的博客 173

代码证明HashMap和ConcurrentHashMap线程的安全问题

代码证明HashMap和ConcurrentHashMap线程的安全问题

ProBaiXiaodi的博客 1151

硬件工程师入门基础知识(一)基础元器件认识(二)

我们的电路输入一般是只有两种状态,0V 或者是其它的高电平(1.8V,3.3V,5V 等),截止状态一般不用怎么考虑,因为如果让三极管的 Vbe=0,自然就截止了,重要的是饱和状态如何保证。也就是说只要 Rb

小幽余生不加糖 1万+

2020年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第3题)

2020年信奥赛C++提高组csp-s初赛真题及答案解析(阅读程序第3题)

王老师青少年编程 990

Spring那级缓存用到了 ConcurrentHashMap

2:allowEarlyReference 的判断提到 synchronized 外面了。真实面试题目,问:Spring 那级缓存用到了 ConcurrentHashMap?老版本 earlySingletonObjects 使用的是 HashMap。org.springframework.boot 2.7.10版本。1:在加锁之前和加锁之后都有两层是否为空的判断,老版本只有一层判断。

李支锋的博客 433

Codex 桌面端自定义模型列表为空 — 修复记录

摘要:Codex桌面端首次启动正常,但二次启动后出现中文界面且模型列表丢失。问题根源在于Electron的Chromium持久化状态损坏,导致UI渲染异常。解决方案包括:1) 将config.toml中的模型路径改为绝对路径;2) 删除损坏的状态文件.codex-global-state.json;3) 清除Chromium用户数据目录AppData\Roaming\Codex\web。验证显示修改后模型功能恢复正常,重装无效的原因是卸载未清除用户数据目录。

猿小白的博客 1152

ConcurrentHashMap是如何实现线程安全的

ConcurrentHashMap是如何实现线程安全的 文章目录ConcurrentHashMap是如何实现线程安全的前言相关概念Amdahl定律初始化数据结构时的线程安全总结put操作的线程安全总结扩容操作的线程安全扩容时的get操作多线程协助扩容在什么情况下会进行扩容操作?总结统计容器大小的线程安全假设当前线程为第一个put的线程出现了线程竞争导致CAS失败计数桶扩容总结get操作的线程安全总...

|-| [- |_ |_ () 4万+

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Java避坑案例 - ConcurrentHashMap 的使用陷阱

JDK 1.5 后推出的,是一个高性能的线程安全的哈希表容器。“线程安全”这四个字特别容易让人误解,因为只能保证提供的原子性读写操作是线程安全的.我们通常误以为使用了后,就不需要担心线程安全问题。然而只能保证单个操作(如putget)的线程安全,无法确保多个操作(如size和putAll)之间的一致性。使用并发工具类的注意事项多操作间状态一致性问题能保证单次操作的线程安全,但无法保证多个操作之间的一致性。避免使用快照方法控制流程在并发情况下,size()等方法只能用作参考,不能用于控制流程逻辑。

小工匠 5126

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

ConcurrentHashMap是如何保证线程安全的

ConcurrentHashMap是如何保证线程安全的 之前分析过HashMap的一些实现细节,关于HashMap你需要知道的一些细节, 今天我们从源码角度来看看ConcurrentHashMap是如何实现线程安全的,其实网上这类文章分析特别多,秉着”纸上得来终觉浅,绝知此事要躬行“的原则,我们尝试自己去分析下,希望这样对于ConcurrentHashMap有一个更深刻的理解。 为什么说HashM...

JasonGaoH的专栏 6735

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

ConcurrentHashMap线程安全吗?

没啥深入实践的理论系同学,在使用并发工具时,总是认为把HashMap改为ConcurrentHashMap,就完美解决并发了呀。或者使用写时复制的CopyOnWriteArrayList,性能更佳呀!技术言论虽然自由,但面对魔鬼面试官时,我们更在乎的是这些真的正确吗? 1 线程重用导致用户信息错乱 生产环境中,有时获取到的用户信息是别人的。查看代码后,发现是使用了ThreadLocal缓存获取到的用户信息。 ThreadLocal适用于变量在线程间隔离,而在方法或类间共享的场景。 若用户信息的获取比较

qq_24692021的博客 3831

面试官:ConcurrentHashMap真的线程安全吗?

没有真正实践过的朋友,总是认为把HashMap改为,就完美解决并发了呀。或者使用写时复制的,性能更佳呀!技术言论虽然自由。但面对魔鬼面试官时,我们更在乎的是这些真的正确吗?不要只会用并发工具,而不熟悉线程原理不要觉得用了并发工具,就怎么都线程安全不熟悉并发工具的优化本质,就难以发挥其真正性能不要不结合当前业务场景,就随意选用并发工具,可能导致系统性能更差。

fuladulu的博客 938

HashTable和ConcurrentHashMap是如何实现线程安全的?

在说HashTable和ConcurrentHashMap线程安全问题之前我们先看一段代码: public class Safe { private int count = 0; private void add(){ for(int i=0 ; i&lt;100 ; i++){ try { Thr...

码眼的博客 1万+

JDK8的ConcurrentHashMap也会造成CPU 100%

转载:不止 JDK7 的 HashMap ,JDK8 的 ConcurrentHashMap 也会造成 CPU 100%?原因与解决~ 现象 大家可能都听过JDK7中的HashMap在多线程环境下可能造成CPU 100%的现象,这个由于在扩容的时候put时产生了死链,由此会在get时造成了CPU 100%。这个问题在JDK8中的HashMap获得了解决。其实JDK7中的HashMap...

350

Java面试-ConcurrentHashMap 如何实现线程安全

本文深入剖析Java并发集合ConcurrentHashMap的线程安全实现机制,对比JDK7和JDK8两代核心设计差异。JDK7采用分段锁(Segment)机制,通过16个独立锁段实现有限并发;而JDK8创新性地结合CAS操作与synchronized关键字,实现更细粒度的桶级别锁控制。文章通过源码解析、结构图解和性能对比,揭示其如何在高并发场景下兼顾线程安全与操作效率,同时回答常见的6个面试高频问题。

千淘万漉虽辛苦,吹尽狂沙始到金 2万+

ConcurrentHashMap、synchronized与线程安全

最近做的项目中遇到一个问题:明明用了ConcurrentHashMap,可是始终线程不安全 除去项目中的业务逻辑,简化后的代码如下: public class Test40 { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i

我想跟代码谈谈 2万+

ConcurrentHashMap真的线程安全吗?

作者|JavaEdge来源 | JavaEdge(ID:Java-Edge)头图 | 优快云下载自东方IC没啥深入实践的理论系同学,在使用并发工具时,总是认为把HashMap改...

优快云资讯 6077

ConcurrentHashMap原理深度拆解:从锁机制到实战避坑

HashMap线程不安全还死循环?Hashtable并发性能烂到爆?别慌,ConcurrentHashMap才是Java并发编程的“救星”!本文从面试高频问题切入,带你吃透ConcurrentHashMap的核心价值——为什么它能兼顾线程安全与高并发? 内容全程干货:JDK 1.7分段锁与JDK 1.8“CAS+synchronized”的锁机制演进全解析,附核心源码片段;volatile、CAS、精准锁三大安全法宝深度拆解;还有putIfAbsent原子操作、弱一致性迭代器等实战避坑指南。

qq_59219765的博客 846

面试阿里被P8质问:ConcurrentHashMap真的线程安全吗?

没有啥深入实践的理论派同学,在使用并发工具时,总是认为把HashMap改为ConcurrentHashMap,就完美解决并发了呀。或者使用写时复制的CopyOnWriteArrayList,性能更佳呀!技术言论虽然自由,但面对魔鬼面试官时,我们更在乎的是这些真的正确吗? 诚然,JDK 并发工具是优秀的。但也是建立在使用者清楚其适用场景基础上,才能最大发挥并发性能。 1 线程重用导致用户信息错乱 生产环境中,有时获取到的用户信息是别人的。查看代码后,发现是使用了ThreadLocal缓存获取到的用户信息。 T

JavaEdge全是干货的技术号 3384
上一篇: 华为OD机试 - 栈数据合并/空栈压数 - 栈(Python/JS/C/C++ 2024 E卷 100分)
下一篇: 3个真实案例,彻底吃透读写锁ReentrantReadWriteLock
哪 吒
哪 吒 2022年度博客之星TOP 1 2022年度博客之星TOP 1 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄9年 59万+粉丝 2933原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哪 吒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值