当前位置: 首页 > article >正文

【JVM深度解析】第27篇:并发编程实战案例与陷阱

摘要理论千遍不如实践一遍。本文通过六个真实场景的并发问题展示多线程编程中的常见陷阱线程池 OOM、ThreadLocal 内存泄漏、双重检查锁单的隐藏危险、HashMap 并发死循环、生产者消费者模式死锁、以及 CountDownLatch 误用导致的测试失败。每个案例都包含问题描述、根因分析、解决方案和验证代码。掌握这些你才能避免踩坑写出正确高效的并发代码。案例一线程池 OOM1.1 问题描述// 问题代码publicclassThreadPoolOOM{// 线程池核心线程数为 100队列无限大privatestaticfinalExecutorServiceexecutorExecutors.newFixedThreadPool(100);publicvoidprocess(ListTasktasks){for(Tasktask:tasks){executor.submit(()-{// 处理任务doProcess(task);});}}}// 根因分析// - newFixedThreadPool(100) 实际创建了 LinkedBlockingQueue()// - LinkedBlockingQueue 默认容量 Integer.MAX_VALUE// - 当任务提交速度 处理速度时队列无限增长// - 导致 OOM1.2 解决方案// 解决方案限制队列大小publicclassThreadPoolOOMFixed{privatestaticfinalintCORE_POOL_SIZE100;privatestaticfinalintMAX_POOL_SIZE100;privatestaticfinalintQUEUE_SIZE1000;privatestaticfinalExecutorServiceexecutornewThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,0L,TimeUnit.MILLISECONDS,newLinkedBlockingQueue(QUEUE_SIZE),// 限制队列大小newThreadPoolExecutor.CallerRunsPolicy()// 拒绝策略调用者执行);}案例二ThreadLocal 内存泄漏2.1 问题描述// 问题代码publicclassUserContextHolder{publicstaticfinalThreadLocalUsercurrentUsernewThreadLocal();}// Spring MVC 控制器publicclassUserController{GetMapping(/user/{id})publicUsergetUser(PathVariableLongid){UseruseruserService.getById(id);UserContextHolder.currentUser.set(user);// 设置用户// 处理请求...// 问题忘记清理// 线程池复用时ThreadLocal 中的数据不会被 GC 回收returnuser;}}2.2 解决方案// 方案一finally 中清理publicclassUserControllerFixed{GetMapping(/user/{id})publicUsergetUser(PathVariableLongid){UseruseruserService.getById(id);UserContextHolder.currentUser.set(user);try{// 处理请求returnprocessUser(user);}finally{UserContextHolder.currentUser.remove();// 清理}}}// 方案二使用框架的请求上下文推荐publicclassUserContextHolder2{publicstaticfinalThreadLocalUserContextcontextHoldernewThreadLocal();}// 方案三使用阿里 TransmittableThreadLocal异步场景publicclassTtlExample{// TransmittableThreadLocal 会在子线程中自动继承父线程的值// 且在线程池复用时自动处理清理}案例三HashMap 并发死循环3.1 问题描述// 问题代码publicclassConcurrentHashMapOOM{privateMapString,ObjectcachenewHashMap();publicvoidput(Stringkey,Objectvalue){cache.put(key,value);// HashMap 不是线程安全的}publicObjectget(Stringkey){returncache.get(key);}}// 根因分析// 并发 put 时HashMap 可能发生// 1. 扩容时链表成环// 2. 数据丢失// 3. get 时死循环CPU 100%3.2 解决方案// 方案一ConcurrentHashMapprivateMapString,ObjectcachenewConcurrentHashMap();publicvoidput(Stringkey,Objectvalue){cache.put(key,value);// 线程安全}// 方案二Collections.synchronizedMap性能较差privateMapString,ObjectcacheCollections.synchronizedMap(newHashMap());// 方案三Guava Cache推荐privateLoadingCacheString,ObjectcacheCacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(Duration.ofMinutes(10)).build(newCacheLoaderString,Object(){OverridepublicObjectload(Stringkey)throwsException{returnloadFromDB(key);}});案例四双重检查锁的陷阱4.1 问题描述// 错误版本publicclassSingletonBroken{privatestaticSingletonBrokeninstance;publicstaticSingletonBrokengetInstance(){if(instancenull){// T1: 第一个检查synchronized(SingletonBroken.class){if(instancenull){// T2: 第二个检查instancenewSingletonBroken();// 问题这行代码不是原子的// 分解为// 1. 分配内存// 2. 调用构造函数// 3. 写入 instance 引用// 可能发生重排序1 - 3 - 2}}}returninstance;}}// 危险场景// 线程 A 进入同步块执行了 instance new SingletonBroken(); 的步骤 1 和 3// 线程 B 检查 instance ! null直接返回 instance但对象还未初始化4.2 解决方案// 方案一volatile推荐publicclassSingletonFixed{privatestaticvolatileSingletonFixedinstance;publicstaticSingletonFixedgetInstance(){if(instancenull){synchronized(SingletonFixed.class){if(instancenull){instancenewSingletonFixed();}}}returninstance;}}// 方案二饿汉式类加载时就初始化publicclassSingletonHungry{privatestaticfinalSingletonHungryinstancenewSingletonHungry();privateSingletonHungry(){}publicstaticSingletonHungrygetInstance(){returninstance;}}// 方案三静态内部类延迟加载 线程安全publicclassSingletonInner{privateSingletonInner(){}privatestaticclassHolder{staticfinalSingletonInnerinstancenewSingletonInner();}publicstaticSingletonInnergetInstance(){returnHolder.instance;// 类加载时由 JVM 保证线程安全}}案例五CountDownLatch 误用5.1 问题描述// 错误代码publicclassCountDownLatchWrong{privatestaticfinalintTHREAD_COUNT10;TestpublicvoidtestConcurrent()throwsInterruptedException{CountDownLatchstartSignalnewCountDownLatch(1);// 错误CountDownLatchdoneSignalnewCountDownLatch(THREAD_COUNT);for(inti0;iTHREAD_COUNT;i){newThread(()-{try{startSignal.await();// 等待开始信号doWork();doneSignal.countDown();}catch(InterruptedExceptione){Thread.currentThread().interrupt();}}).start();}// 错误主线程等待 doneSignal但没有先触发 startSignaldoneSignal.await();// 永远等待下去}}5.2 解决方案// 正确代码publicclassCountDownLatchCorrect{privatestaticfinalintTHREAD_COUNT10;TestpublicvoidtestConcurrent()throwsInterruptedException{CountDownLatchstartSignalnewCountDownLatch(1);CountDownLatchdoneSignalnewCountDownLatch(THREAD_COUNT);for(inti0;iTHREAD_COUNT;i){newThread(()-{try{startSignal.await();// 所有线程先等待doWork();}finally{doneSignal.countDown();}}).start();}// 正确顺序先发送开始信号再等待完成Thread.sleep(100);// 确保所有线程都已就绪startSignal.countDown();// 发送开始信号doneSignal.await();// 等待所有线程完成assertTrue(所有线程应该执行完成,doneSignal.getCount()0);}}案例六生产者消费者死锁6.1 问题描述// 问题代码publicclassProducerConsumerDeadlock{privateBlockingQueueStringqueuenewLinkedBlockingQueue(10);publicvoidproduce()throwsInterruptedException{while(true){StringdatagenerateData();queue.put(data);// 阻塞直到队列不满}}publicvoidconsume()throwsInterruptedException{while(true){Stringdataqueue.take();// 阻塞直到队列不空process(data);}}}// 问题// 队列容量为 10// 如果只启动一个生产者它会一直生产直到队列满// 如果只启动一个消费者它会一直等待// 如果生产者和消费者同时启动队列满了后两者都阻塞// 但如果队列为空且消费者多于生产者 → 死锁6.2 解决方案// 方案使用线程池 信号量控制publicclassProducerConsumerFixed{privatefinalSemaphoreproducerSemaphorenewSemaphore(10);privatefinalSemaphoreconsumerSemaphorenewSemaphore(0);privatefinalQueueStringqueuenewLinkedList();publicvoidproduce()throwsInterruptedException{while(true){StringdatagenerateData();producerSemaphore.acquire();// 获取生产许可synchronized(queue){queue.add(data);}consumerSemaphore.release();// 释放消费许可}}publicvoidconsume()throwsInterruptedException{while(true){consumerSemaphore.acquire();// 获取消费许可Stringdata;synchronized(queue){dataqueue.poll();}process(data);producerSemaphore.release();// 释放生产许可}}}总结并发编程的陷阱无处不在线程池 OOM、ThreadLocal 泄漏、HashMap 并发、Double-Checked Locking、CountDownLatch 误用、生产者消费者死锁……每一种都是血泪教训。核心原则使用线程安全的集合、使用 ThreadLocal 时务必清理、使用 volatile/CAS/synchronized 保证可见性和原子性、使用限流和超时防止资源耗尽。系列导航上一篇【JVM深度解析】第26篇CAS、AQS与并发工具类原理下一篇【JVM深度解析】第28篇JVM发展史从Sun到Oracle系列目录JVM深度解析参考资料Java Concurrency in PracticeJava ThreadLocal - BaeldungDouble-Checked Locking - Wikipedia

相关文章:

【JVM深度解析】第27篇:并发编程实战案例与陷阱

摘要 理论千遍不如实践一遍。本文通过六个真实场景的并发问题,展示多线程编程中的常见陷阱:线程池 OOM、ThreadLocal 内存泄漏、双重检查锁单的隐藏危险、HashMap 并发死循环、生产者消费者模式死锁、以及 CountDownLatch 误用导致的测试失败。每个案例…...

5分钟上手ChemCrow:用AI化学助手完成专业级分析

5分钟上手ChemCrow:用AI化学助手完成专业级分析 【免费下载链接】chemcrow-public Chemcrow 项目地址: https://gitcode.com/gh_mirrors/ch/chemcrow-public 你是否曾为复杂的化学分析任务感到头疼?计算分子量、查询专利状态、预测化学反应产物&a…...

新手避坑指南:用RK3576开发板点亮MIPI-DSI屏幕,从接线到配置的完整流程

RK3576开发板实战:MIPI-DSI屏幕连接与配置避坑手册 第一次拿到RK3576开发板和MIPI-DSI屏幕时,那种既兴奋又忐忑的心情我至今记忆犹新。作为嵌入式开发的新手,面对密密麻麻的接口和陌生的术语,最担心的莫过于一个不小心就把几千块的…...

从MOVED错误到丝滑重定向:深入理解Redis集群的客户端寻址机制

从MOVED错误到丝滑重定向:深入理解Redis集群的客户端寻址机制 第一次在Redis集群中执行SET user:1001 "Alice"命令时,看到终端返回(error) MOVED 1234 192.168.1.2:6381的错误信息,我愣了几秒钟。作为一个习惯了单机Redis的开发者&…...

Bootstrap5 进度条

Bootstrap5 进度条 随着互联网技术的不断发展,前端开发工具和框架也在不断更新迭代。Bootstrap 作为全球最受欢迎的前端框架之一,其版本更新也备受关注。Bootstrap5 作为最新版本,在保持原有优势的基础上,也带来了一些新的功能和改进。本文将详细介绍 Bootstrap5 中进度条…...

7815与7915核心区别解析

7815与7915均为三端线性稳压集成电路,但其核心区别在于输出电压的极性:7815输出稳定的**15V正电压,而7915输出稳定的-15V**负电压。它们通常成对使用,为需要正负对称电源的模拟电路(如运算放大器、音频放大器&#xff…...

零基础玩转Sambert语音合成:开箱即用版,5分钟搭建AI配音系统

零基础玩转Sambert语音合成:开箱即用版,5分钟搭建AI配音系统 1. 引言:为什么选择开箱即用的语音合成? 想象一下,你正在制作一个短视频,需要给画面配上生动的旁白。传统方法要么自己录音,要么花…...

掌握RDKit化学信息学工具:从分子计算到药物发现的完整实战指南

掌握RDKit化学信息学工具:从分子计算到药物发现的完整实战指南 【免费下载链接】rdkit The official sources for the RDKit library 项目地址: https://gitcode.com/gh_mirrors/rd/rdkit RDKit作为现代化学信息学的核心工具包,为化学家、药物研发…...

无人机强化学习终极指南:如何用gym-pybullet-drones快速构建专业仿真环境

无人机强化学习终极指南:如何用gym-pybullet-drones快速构建专业仿真环境 【免费下载链接】gym-pybullet-drones PyBullet Gymnasium environments for single and multi-agent reinforcement learning of quadcopter control 项目地址: https://gitcode.com/gh_m…...

PvZ Toolkit:植物大战僵尸PC版终极修改指南

PvZ Toolkit:植物大战僵尸PC版终极修改指南 【免费下载链接】pvztoolkit 植物大战僵尸 PC 版综合修改器 项目地址: https://gitcode.com/gh_mirrors/pv/pvztoolkit PvZ Toolkit是一款功能强大的植物大战僵尸PC版综合修改器,专为玩家打造个性化游戏…...

快速部署MT5文本增强工具:支持批量生成,提升工作效率

快速部署MT5文本增强工具:支持批量生成,提升工作效率 1. 工具简介与核心价值 MT5文本增强工具是一款基于阿里达摩院mT5模型开发的本地化NLP工具,专为中文文本处理场景设计。它能快速生成语义相同但表达多样的句子变体,有效解决数…...

EmojiOne Color彩色字体实战指南:打造生动表情符号的高效方案

EmojiOne Color彩色字体实战指南:打造生动表情符号的高效方案 【免费下载链接】emojione-color OpenType-SVG font of EmojiOne 2.3 项目地址: https://gitcode.com/gh_mirrors/em/emojione-color EmojiOne Color是一款基于OpenType-SVG格式的开源彩色表情字…...

从‘阴谋论’到代码:用Python和PyTorch亲手实现Dropout,搞懂训练测试为啥要‘精分’

从神经元"社交恐惧症"到代码实战:用Python拆解Dropout的双面人生 想象一下你正在组织一场大型团队建设活动——如果每次分组时都强制打乱成员组合,禁止小团体固化,会发生什么?那些总依赖特定搭档的"社交恐惧型&quo…...

ABAP2XLSX企业级Excel生成技术选型指南:5大优势与架构深度解析

ABAP2XLSX企业级Excel生成技术选型指南:5大优势与架构深度解析 【免费下载链接】abap2xlsx Generate your professional Excel spreadsheet from ABAP 项目地址: https://gitcode.com/gh_mirrors/ab/abap2xlsx 一、技术价值定位:为什么选择ABAP2X…...

零代码网页抓取神器:Web Scraper Chrome扩展完整指南

零代码网页抓取神器:Web Scraper Chrome扩展完整指南 【免费下载链接】web-scraper-chrome-extension Web data extraction tool implemented as chrome extension 项目地址: https://gitcode.com/gh_mirrors/we/web-scraper-chrome-extension 想要从任何网站…...

终极游戏存档备份方案:Ludusavi让你的游戏进度永不丢失 [特殊字符]

终极游戏存档备份方案:Ludusavi让你的游戏进度永不丢失 🎮 【免费下载链接】ludusavi Backup tool for PC game saves 项目地址: https://gitcode.com/gh_mirrors/lu/ludusavi 你是否曾因系统重装、硬盘故障或意外删除而失去宝贵的游戏进度&#…...

从图像分割到目标检测:膨胀卷积(空洞卷积)的核心原理与实战调优

1. 为什么我们需要膨胀卷积? 我第一次接触膨胀卷积是在做医学图像分割项目的时候。当时遇到一个头疼的问题:用传统卷积神经网络做肝脏CT图像分割时,小肿瘤总是检测不出来。反复调整网络结构后发现,问题出在感受野上——普通卷积层…...

Windows 11 LTSC 24H2 如何快速安装微软商店:完整解决方案

Windows 11 LTSC 24H2 如何快速安装微软商店:完整解决方案 【免费下载链接】LTSC-Add-MicrosoftStore Add Windows Store to Windows 11 24H2 LTSC 项目地址: https://gitcode.com/gh_mirrors/ltscad/LTSC-Add-MicrosoftStore 对于使用 Windows 11 LTSC 24H2…...

Tinder联合World推身份验证:前往验证球验证,可获五次免费推广及“已验证人类徽章”

Tinder携手World ID:面部扫描验证解锁免费推广Tinder用户通过前往World公司的身份验证球进行面部扫描,证明自己是真实人类后,可在应用程序中获得五次免费推广机会。这一服务源于去年World在日本的试点项目,如今正拓展至包括日本和…...

软件考古:咕咕文本背后的开发者工具文化

在互联网软件发展的历史长河中,有许多像咕咕文本这样的小工具曾经闪耀一时。 它们或许没有庞大的用户基数,或许没有持续的商业运营,但在特定的历史时期,它们解决了特定人群的实际问题。 今天,让我们以软件考古的视角…...

Windows安装APK文件的最佳工具:APK Installer全面指南

Windows安装APK文件的最佳工具:APK Installer全面指南 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 还在为Windows电脑无法直接安装安卓应用而烦恼吗&…...

YimMenu:GTA V 终极安全增强菜单的完整指南

YimMenu:GTA V 终极安全增强菜单的完整指南 【免费下载链接】YimMenu YimMenu, a GTA V menu protecting against a wide ranges of the public crashes and improving the overall experience. 项目地址: https://gitcode.com/GitHub_Trending/yi/YimMenu Y…...

JMeter实战指南:从零构建高效接口自动化测试框架

1. 为什么你需要JMeter自动化测试框架 第一次接触JMeter时,我也以为它只是个简单的接口测试工具。直到项目进入快速迭代阶段,我才发现手工维护上百个测试用例有多痛苦——每次需求变更都要逐个修改脚本,测试数据混杂在请求中难以维护&#xf…...

QobuzDownloaderX-MOD:如何轻松下载Qobuz高品质音乐到本地

QobuzDownloaderX-MOD:如何轻松下载Qobuz高品质音乐到本地 【免费下载链接】QobuzDownloaderX-MOD Downloads streams directly from Qobuz. Experimental refactoring of QobuzDownloaderX by AiiR 项目地址: https://gitcode.com/gh_mirrors/qo/QobuzDownloader…...

基于Anything V5的Stable Diffusion服务:5分钟部署教程

基于Anything V5的Stable Diffusion服务:5分钟部署教程 1. 快速了解Anything V5 Anything V5是当前最受欢迎的动漫风格生成模型之一,基于Stable Diffusion技术构建。相比前代版本,V5在以下方面有显著提升: 画质增强&#xff1a…...

建站系统是什么?类型、选择标准与常见系统对比

建站系统,顾名思义,是用于创建和管理网站的软件工具或平台。它帮助用户在不编写代码、不深入理解服务器技术的情况下,完成网站的设计、内容发布和功能配置。你可以这样理解:如果说“网站建设”是盖房子,那么“建站系统…...

智慧医疗药盒药品包装盒检测数据集VOC+YOLO格式3000张1类别

注意数据集中图片有增强图片,有很多是对一个药盒进行不同角度拍摄,所有图片里面都是一个药盒数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件)图片数量(jp…...

把 MQTT 带进 ABAP 栈之后,ABAP Platform 1809 的事件驱动能力终于有了真正的外向接口

很多人在第一次看到 MQTT client in ABAP Platform 1809 这个主题时,会下意识把它理解成,ABAP 又多了一种能发消息的协议而已。真把官方资料和示例代码一路看完,感受会完全不一样。这里新增的并不只是一个 publish 动作,而是 ABAP 终于可以用比较自然的方式,直接接到外部消…...

快手视频下载终极指南:如何轻松获取无水印高清视频

快手视频下载终极指南:如何轻松获取无水印高清视频 【免费下载链接】KS-Downloader 快手(KuaiShou)视频/图片下载工具;数据采集工具 项目地址: https://gitcode.com/gh_mirrors/ks/KS-Downloader 还在为无法保存喜欢的快手…...

android 14.0 framework下service下引用 opt目录下相关类编译不过的功能实现

1.前言 在14.0的系统rom定制化开发中,在某些产品中,对于在service下引用framewroks/opt下面的类 比如GsmSMSDispatcher类等,会出现找不到文件类的问题,接下来分析下相关问题的原因,然后 解决这个问题 2.framework下service下引用 opt目录下相关类编译不过的功能实现的核…...