解决 Java 中由于 parallelStream 导致的死锁

news2024/11/18 20:00:52

并发性是软件开发的福音,也是祸根。通过并行处理提高性能的承诺与错综复杂的挑战相伴而生,例如臭名昭著的死锁。死锁是多线程编程世界中的隐患,它甚至可以使最强大的应用程序陷入瘫痪。它描述了两个或多个线程永远被阻塞,相互等待的情况。

在这篇博文中,我们深入探讨了现实世界中因使用 Java 的“ parallelStream ”而引发的死锁事件。我们将剖析根本原因,仔细检查线程堆栈跟踪。

场景

想象一下一个平静的代码库,它利用 Java 的“parallelStream”对 Collection 进行处理以提高处理速度。然而,随着我们的应用程序变得越来越复杂,出现了一个意想不到的隐蔽问题——死锁。线程曾经是盟友,现在却陷入了矛盾的境地。在这篇文章的后面,我们将仔细研究一些线程的堆栈跟踪,其中的主角是“ForkJoinPool.commonPool-worker-0”和“ForkJoinPool.commonPool-worker-1”。

以下两个线程堆栈跟踪:

线程 1:ForkJoinPool.commonPool-worker-0

stackTrace:

java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.app.DataParser.read(DataParser.java:58)
- waiting to lock <0x00000001d6ff09f0> (a com.example.app.DataParser)
at com.example.app.ObjectLoader.read(ObjectLoader.java:196)
at com.example.app.MemorySnapshot$ObjectCacheManager.load(MemorySnapshot.java:2152)
at com.example.app.MemorySnapshot$ObjectCacheManager.load(MemorySnapshot.java:1)
at com.example.app.ObjectCache.get(ObjectCache.java:52)
- locked <0x00000001d6fafb00> (a com.example.app.MemorySnapshot$ObjectCacheManager)
at com.example.app.MemorySnapshot.getObject(MemorySnapshot.java:1453)
:
:
:
at com.example.app.AnalyzerImpl.lambda$37(AnalyzerImpl.java:3248)
at com.example.app.AnalyzerImpl$$Lambda$150/179364589.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:747)
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:721)
at java.util.stream.AbstractTask.compute(AbstractTask.java:327)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:175)
Locked ownable synchronizers:
- None

线程2:ForkJoinPool.commonPool-worker-1

stackTrace:

java.lang.Thread.State: BLOCKED (on object monitor)
at com.example.app.ObjectCache.get(ObjectCache.java:44)
- waiting to lock <0x00000001d6fafb00> (a com.example.app.MemorySnapshot$ObjectCacheManager)
at com.example.app.MemorySnapshot.getObject(MemorySnapshot.java:1453)
at com.example.app.DataParser.readObjectArrayDump(DataParser.java:135)
at com.example.app.DataParser.read(DataParser.java:65)
- locked <0x00000001d6ff09f0> (a com.example.app.DataParser)
at com.example.app.ObjectLoader.read(ObjectLoader.java:196)
at com.example.app.ObjectInstance.read(ObjectInstance.java:135)
- locked <0x00000001e5822bf8> (a com.example.app.ObjectInstance)
at com.example.app.ObjectInstance.getAllFields(ObjectInstance.java:101)
:
:
:
at com.example.app.AnalyzerImpl.lambda$37(AnalyzerImpl.java:3248)
at com.example.app.AnalyzerImpl$$Lambda$150/179364589.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:747)
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:721)
at java.util.stream.AbstractTask.compute(AbstractTask.java:327)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:175)
Locked ownable synchronizers:
- None

线程堆栈跟踪让我们得以一窥死锁的核心。有趣的是,两个线程都纠缠在同一个方法中 — — 这清楚地表明它们正在争夺一个共享资源。有问题的资源是一个对象监视器,它通常是死锁场景中的罪魁祸首。

进一步分析表明,这些线程正在尝试执行需要独占访问“ com.example.app ”包内的对象缓存的操作。这种对资源访问的竞争是导致死锁的根本原因。这种资源争用构成了死锁的核心,每个线程都在等待对方放弃控制权。令人惊讶的是,这场灾难的罪魁祸首是人们所熟悉的“ parallelStream ”……

罪魁祸首:parallelStream

更深入地研究这种情况,我们可以在 Collection 上使用 Java 的parallelStream* 。对于不熟悉的人, “parallelStream”是一种旨在通过利用并行性来提高处理速度的机制。虽然“parallelStream”机制有望通过利用并行性来加速处理,但它可能会带来复杂情况,包括死锁。*

陷入死锁的线程都使用了默认的“fork-join 池”——一种促进并行操作的共享资源。我们的线程不知不觉地但不可避免地成为了该池中同一资源的竞争对手。这个共享池无意中造成了一种情况,即线程最终争夺资源,从而导致死锁。

更顺畅的道路:选择 Stream 而不是 ParallelStream

在解开死锁难题的过程中,我们发现了改变游戏规则的因素。在解开错综复杂的死锁网络时,我们意识到集合上使用 parallelStream 无意中加剧了资源争用。

我们在集合中使用 parallelStream 就像邀请多个朋友同时使用同一个玩具一样——这会导致很多推搡。这种骚动导致我们的线程发生冲突并导致死锁。但我们没有放弃;我们决定换一种玩法。

我们没有让所有人一起玩玩具,而是让他们轮流玩。我们从“parallelStream”切换到一种更简单的方式——只需“流式传输”。这一变化意味着一次只有一个朋友玩玩具。这种友好的轮流减少了打架的机会,并使我们的线程一起工作而不会发生冲突。

这种切换不仅仅是改变代码中的一个单词;它就像在游戏中选择一种新策略。猜猜怎么着?它成功了!线程现在一个接一个地运行,不再相互碰撞。这意味着不再有死锁,我们的应用程序可以松一口气了。

下面,您将看到突出显示这一关键转变的代码片段:

原始代码:

List<?> elements = …

elements.parallelStream().map(e -> {

   // Some code here that is causing deadlock…

}).collect(Collectors.toList());

修订后的代码:

List<?> elements = …

elements.stream().map(e -> {

   // Some code here that was causing deadlock…

}).collect(Collectors.toList());

带来重大改变

我们的死锁挑战表明,小的改变可以产生巨大的效果。从“parallelStream”切换到常规流虽然简单,但却带来了显著的变化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2172721.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FOC矢量控制

目录 前言一、FOC简介1.1 FOC是什么1.2 FOC框图介绍 二、FOC坐标变换2.1 电流采集2.2 Clarke变换2.3 Park变换 三、闭环控制3.1 电流环控制3.3 速度环控制3.4 位置环控制 四、SVPWM原理4.1 空间矢量合成4.2 SVPWM法则4.3 MOS开关方式4.4 矢量作用时间 前言 本文主要介绍无刷直流…

未来医疗:从医技数字化2.0到全局变革

斯蒂芬•申弗&#xff08;Stephen C. Schimpff&#xff09;的《医疗大趋势&#xff1a;明日医学》被认为是全球第一本系统介绍未来医疗的权威著作。在书中&#xff0c;作者认为基因组学、手术技术革新、干细胞、数字医疗等关键技术将驱动医疗变革的发生&#xff0c;全面提升人类…

OpenAI o1-preview:详细分析

OpenAI 终于打破沉默&#xff0c;发布了万众期待的 “o1-preview”。其中有很多内容值得解读。 作为一家以 LLM 为生的人工智能初创公司&#xff0c;我们想知道这个新模型的性能如何&#xff0c;以及它能如何帮助我们改进产品。 因此&#xff0c;我花了一整天的时间来实验这个…

(JAVA)队列 和 符号表 两种数据结构的实现

1. 队列 1.1 队列的概述 队列是一种基于先进先出&#xff08;FIFO&#xff09;的数据结构&#xff0c;是一种只能在一端进行插入&#xff0c;在另一端进行删除操作的特殊线性表。 它按照先进先出的原则存储数据&#xff0c;先进入的数据&#xff0c;在读取时先被读出来 1.2 …

蓝桥杯【物联网】零基础到国奖之路:十二. TIM

蓝桥杯【物联网】零基础到国奖之路:十二. TIM 第一节 理论知识第二节 cubemx配置 第一节 理论知识 STM32L071xx器件包括4个通用定时器、1个低功耗定时器&#xff08;LPTIM&#xff09;、2个基本定时器、2个看门狗定时器和SysTick定时器。 通用定时器&#xff08;TIM2、TIM3、…

详解JavaScript中属性getter和setter

6.6 属性getter和setter 属性值可以用1个或者2个方法替代&#xff0c;getter和setter. 由这两个定义的属性称作存取器属性(accessor property)&#xff0c;不同于数据属性&#xff0c;只有一个简单的值。有读写属性&#xff0c;只能写&#xff0c;只能读&#xff0c;可以读写…

数据结构 算法的时间复杂度 计算(两种规则 加法原则+乘法原则)

在分析时间复杂性时&#xff0c;加法和乘法原则是两个基本且重要的概念&#xff0c;它们分别用于处理算法中顺序执行和嵌套执行的代码段的时间复杂度计算。以下是对这两个原则的详细说明&#xff1a; 一、加法原则 定义&#xff1a; 加法原则适用于顺序执行的代码段。如果一…

从Linux系统的角度看待文件-基础IO

目录 从Linux系统的角度看待文件 系统文件I/O open write read 文件操作的本质 vim中批量注释的方法 从Linux系统的角度看待文件 关于文件的共识&#xff1a; 1.空文件也要占用磁盘空间 2.文件内容属性 3.文件操作包括文件内容/文件属性/文件内容属性 4.文件路径文…

LDO实习报告(免费)-有完整电路版图

LDO实习任务书 实习目的&#xff1a; 了解LDO电路研究现状和原理结构&#xff0c;熟悉模拟电路设计流程。 week1 &#xff1a; 调研LDO应用情况及研究现状。 week2 &#xff1a; 熟悉LDO基本原理及组成。 week3 &#xff1a; 构建LDO电路。 week4 &#xff1a; 对LDO进…

从日志到洞察:轻松实现服务器安全管理的神器

在当今复杂多变的网络环境中&#xff0c;服务器安全管理已成为一项不可或缺的任务。然而&#xff0c;面对海量的日志数据&#xff0c;如何快速精准地提取有价值的信息&#xff0c;并及时发现潜在的安全威胁&#xff1f;本文将为您介绍一款强大的服务器日志检索与查杀工具&#…

pilz皮尔兹PSSuniversal分散控制平台 Dezentrale Steuerungsplattform 手测

pilz皮尔兹PSSuniversal分散控制平台 Dezentrale Steuerungsplattform 手测

WebAPI编程(第三天,第四天)

WebAPI编程&#xff08;第三天&#xff0c;第四天&#xff09; day03 - Web APIs1.1. 节点操作1.1.1 删除节点1.1.2 案例&#xff1a;删除留言1.1.3 复制&#xff08;克隆&#xff09;节点1.1.4 案例&#xff1a;动态生成表格1.1.5 创建元素的三种方式1.1.6 innerTHML和createE…

基于SSM+小程序的自习室选座与门禁管理系统(自习室1)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 1、管理员实现了首页、基础数据管理、论坛管理、公告信息管理、用户管理、座位管理等 2、用户实现了在论坛模块通过发帖与评论帖子的方式进行信息讨论&#xff0c;也能对账户进行在线充值…

深圳龙链科技:全球区块链开发先锋,领航Web3生态未来

【深圳龙链科技】是全球领先的Web3区块链技术开发公司&#xff0c;专注于为全球客户提供创新高效的区块链解决方案。 深圳龙链科技由币安资深股东携手香港领先的Web3创新枢纽Cyberport联袂打造&#xff0c;立足于香港这一国际金融中心&#xff0c;放眼全球&#xff0c;汇聚了华…

罕见,回复问询后闪电终止,业绩存下滑风险

《IPO魔女》认为&#xff0c;和美精艺利润低且大幅波动&#xff0c;报告期公司毛利率持续大幅下滑。而2023年同行业的上市公司均出现了业绩大幅下滑的情况&#xff0c;还未上市的和美精艺恐怕也存在业绩下滑的风险。此外&#xff0c;2020年至2022年&#xff0c;和美精艺研发投入…

Excel根据一个值匹配一行数据

根据一个值从一个表中匹配一行数据&#xff0c;例如从左边的表中找到指定姓名的所有行数据 使用VLOOKUP函数&#xff0c;参数&#xff1a; Lookup_value&#xff1a;需要搜索的值&#xff0c;单个值 Table_array&#xff1a;被搜索的区域&#xff0c;是个表 Col_index_num&…

JS数据类型类型转换

基本数据类型 JS中的数据类型由原始值和对象共同组成&#xff0c;原始值一共有七种原始值&#xff1a; 数值(Number)大整数(BigInt)字符串(String)布尔值(Boolean)空值(Null)未定义(Undefined)符号(Symbol) 数值和大整数 数值(Number):在js中所有的整数和浮点数都是number类型 …

【vue-router】用meta.keepAlive做缓存

网上大家都说按下面的写法 <keep-alive><router-view v-if"route.meta.keepAlive"></router-view> </keep-alive> <router-view v-if"!route.meta.keepAlive"></router-view>但是会报错 解决方法也没找到 最后换一…

23、Presidential

难度 中 目标 root权限 2个flag 基于virtualbox启动 题目提示枚举是你的朋友 kali 192.168.86.102 靶机 192.168.86.107 信息收集 端口扫描 tcp开启的端口就两个&#xff0c;稳妥起见扫了一些常见的端口看是否有UDP协议开放的端口。同时nmap在扫描80端口提示可能存在的tra…

OCR识别系统 YOLOv8 +Paddle 方案落地

YOLOv8 PaddleOCR 技术方案落地 Yolov8相关文档Step 1 证件模型的训练Step 2 Yolov8进行图片推理Step 3 PaddleOCR进行识别Step 4 整合Yolov8 PaddleOCR 进行OCR Yolov8相关文档 《yolov8 官方网站》 《Yolov8 保姆级别安装》 Ultralytics YOLOv8 是一款尖端的、最先进的 (S…