百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

线程池误用导致系统假死(线程池中的线程发生异常还会恢复吗)

nanshan 2025-04-06 19:29 18 浏览 0 评论

背景介绍

在项目中,为了提高系统性能使用了RxJava实现异步方案,其中异步线程池是自建的。但是当QPS稍微增大之后却发现系统假死、无响应和返回,调用方出现大量超时现象。但是通过监控发现,系统线程数正常,内存使用也没有问题,也没有死锁。

在当时没有定位到问题,通过增加一台机器解决了问题。后面也出现同样问题,定位发现是线程池的问题,因为时间紧迫没有深究直接使用了RxJava的Schedulers.io()线程替代自带线程池。最近通过重新学习,发现了问题的原因。

场景模拟

自建线程池

public class ThreadSchedulerUtils {
 private final static ThreadFactory ioFactory = new ThreadFactoryBuilder().setNameFormat("io-pool-%d").build();
 private final static ExecutorService ioTreadPool = new CatExecutorServiceTraceWrapper(new ThreadPoolExecutor(2, 4, 100L, TimeUnit.MICROSECONDS, new LinkedBlockingDeque(4), ioFactory, new ThreadPoolExecutor.AbortPolicy()));

 public static Scheduler getIO() {
 return Schedulers.from(ioTreadPool);
 }
}

模拟调用代码

 @Test
 public void test12() {
 Long aLong = Flowable.fromIterable(IntStream.range(1, 15).mapToObj(i -> i).collect(Collectors.toList())).parallel(8).runOn(ThreadSchedulerUtils.getIO()).map(i -> {
 System.out.println("当前值:" + i + ",线程名称" + Thread.currentThread().getName());
 Thread.sleep(1000L);
 return i;
 }).sequential().count().blockingGet();
 }

运行结果

当前值:1,线程名称io-pool-0
当前值:2,线程名称io-pool-1
当前值:7,线程名称io-pool-2
当前值:8,线程名称io-pool-3
当前值:9,线程名称io-pool-0
当前值:10,线程名称io-pool-1
当前值:3,线程名称io-pool-2
当前值:4,线程名称io-pool-3
当前值:11,线程名称io-pool-2
当前值:6,线程名称io-pool-0
当前值:5,线程名称io-pool-1
当前值:12,线程名称io-pool-3
当前值:14,线程名称io-pool-0
当前值:13,线程名称io-pool-1

结果分析

代码期望的结果是把1到14这15个数据(Flowable.fromIterable(IntStream.range(1, 15).mapToObj(i -> i).collect(Collectors.toList())))并发在4个线程(parallel(8)表示最多有8个线程)上去执行。但是发现真实结果并不是有序的,而是先在0和1线程上执行了1、2,接着在2和3线程上执行了7、8。这种不符合预期的情况是为什么呢?

因为在创建线程池的时候使用的LinkedBlockingDeque。LinkedBlockingDeque队列的执行顺序是这样:

1.如果线程数<=核心线程数,则分配到核心线程数;

2.如果线程数<=核心线程数+队列大小,则不新建线程,在原有线程上轮询执行;

3.如果核心线程数+队列大小<线程数<=核心线程数+队列大小+最大线程数,则会创建新的线程来执行;

4.如果线程数>核心线程数+队列大小+最大线程数,则报错。

通过运行结果来分析可以验证:1,2这两个值在核心线程1,2上执行。然后3-6缓存起来之后队列已满,7和8在新线程2和3上执行。

但是前面说线程数超出之后会报错,为什么测试代码没有报错呢?因为RxJava的Flowable提供背压模式,可以根据下游的处理速度决定发生速度,所以并没有发生报错。

即先缓存满了之后再新建线程,但是在创建线程池的时候理解是创建最大线程数之后还不够再缓存。

原因总结

通过场景复现可以发现,是对线程池的不正确使用,导致可用线程数只为核心线程数,造成大量请求在队列和Flowable里面积压。表现的样子为:系统假死,无法正常处理请求,但是系统的各项指标却是正常的。

改进方案

分为两步方案来解决,第一次采用RxJava自带的线程方法:Schedulers.io();第二种是自己创建缓存线程池。

RxJava自带

RxJava自带的Schedulers.io()线程本质上是一个无边界线程池。可以参考RxJava 内部如何管理线程。但是这种方式有个缺点,没有经过自己的业务封装,不利于业务追踪。例如在监控系统上追踪请求情况。

自己的线程池

为了方便在cat线监控线程执行信息,实现一个和RxJava自带一直的无边界线程池。这种可以根据自己的需求,进行线程池封装。

public class ThreadSchedulerUtils {
 private final static ThreadFactory ioFactory = new ThreadFactoryBuilder().setNameFormat("io-pool-%d").build();
 private final static ExecutorService ioTreadPool = new ThreadPoolExecutor(8, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), ioFactory, new ThreadPoolExecutor.AbortPolicy());

 public static Scheduler getIO() {
 return Schedulers.from(ioTreadPool);
 }
}

小编是一个有着5年工作经验的程序员,关于Java,自己有做材料的整合,一个完整学习Java的路线,学习材料和工具。需要的伙伴可以私信我,发送“Java”后就可免费获取。对于学习Java有任何问题(学习方法,学习效率,如何就业)都可以问我。希望你也能凭自己的努力,成为下一个优秀的程序员!

作者:cleaner_wei

来源:简书


相关推荐

ubuntu24.04下kubernetes1.30环境搭建

设置root用户密码#在Ubuntu系统中,默认情况下root用户是被禁用的(没有设置密码)#而是通过sudo命令让普通用户临时获取管理员权限,#如果需要启用或修改root密...

Canonical 在 Ubuntu 24.10 发布之前对 Snap 进行了更多改进

作为Ubuntu桌面临时工程总监,OliverSmith介绍了Ubuntu24.10的最新进展。在Ubuntu24.10功能冻结之前,GNOME47测试版已经登陆Ubuntu...

Ubuntu Touch OTA-5手机系统发布:细化电源配置等

IT之家8月2日消息,UBports基金会于7月30日发布UbuntuTouch20.04OTA-5版本更新,距离上次OTA-4更新发布相隔6个月时间。Ubuntu...

Ubuntu更契合英特尔酷睿Ultra,综合性能比Win11高15%

IT之家12月23日消息,英特尔本月推出酷睿UltraMeteorLake处理器,那么Win11和Ubuntu发行版两者时间,谁能更好地发挥其性能呢?国外科技媒体phoron...

针对英特尔酷睿CPU优化,Canonical发布Ubuntu实时内核

IT之家7月27日消息,Canonical今天宣布针对支持时序协调运算(TCC)和时间敏感网络(IEEETSN)的英特尔酷睿处理器,推出优化版实时Ubuntu内核。Canonical...

在Ubuntu/Debian上设置永久DNS域名服务器

在Linux上设置自定义DNS服务器可以提高性能和安全性,甚至可以通过DNS阻止一些使用地理屏蔽的网站。有几种方法可以做到这一点,包括在许多Linux发行版中包括的NetworkManagerGUI...

宣布延期:Ubuntu 24.04 LTS第一个版本发布推迟两周

Ubuntu开发团队原计划于8月19日星期四发布Ubuntu24.04.1LTS。然而,由于发现几个重大升级错误,发布被推迟。Ubuntu24.04.1LTS的新发布日期现定为...

Ubuntu系统已经十岁了 10月新推14.10版

|责编:李鑫比较非主流的Ubuntu系统刚刚推出了14.10版,同时大家也可能不知道其实它已经十岁了!它的第一次公布时间为2004年的10月呢。在这次十周年更新中,Ubuntu为用...

wsl2在休眠后的时间偏差问题的修复

笔记本电脑在日常使用中,常常会有进入休眠状态的情况。休眠对于wsl2而言,却造成了时间偏差的问题,休眠期间wsl2的时间停止了。这个问题的根治,需要等微软。本文提供的是一种简单的修复办法。ntp是网络...

基于Ubuntu22.04源码安装配置RabbitVCS过程记录

基于Ubuntu22.04源码安装配置RabbitVCS过程记录安装开始时间开始时间:2025年7月18日17:09(北京时间)系统:Ubuntu22.04用户:itgather时区:A...

GNOME 46桌面环境发布,Ubuntu 24.04 LTS和Fedora 40率先预装

IT之家3月21日消息,GNOME团队今天发布公告,正式推出代号为“Kathmandu”的GNOME46桌面环境,并已经开放下载。Fedora40发行版将于4月发布,率先预装...

如果大家同意的话 Ubuntu可能很快就会有一个新的垃圾桶图标

Ubuntu贡献者目前正在构思一个新的垃圾桶图标,该图标最早可能在10月份Ubuntu25.10发布时出现在Dock栏中。关于Ubuntu垃圾桶图标外观的讨论在2019年持续进...

Ubuntu 25.10 通过更安全地获取时间来提供进一步的安全性提升

Canonical宣布将从Ubuntu25.10开始使用一款名为chrony的软件,以实现更安全的时间管理。最终用户无需过于担心这一变化,但它将增强系统安全性,尤其是在加密操作和证书验证方...

Linux 修改系统时间的两种方式

一:更新系统时间的方式1、手动修改通过相关工具来手动修改系统的时间。2、自动同步使用NTP自动同步系统时间。二:手动修改系统时间1、date工具作用:显示和设置系统时间选项:-d<字符串&g...

Ubuntu计划下版本为RISC-V设置RVA23基线,大量硬件无法升级

IT之家7月14日消息,主要Linux发行版之一的Ubuntu计划在其接下来的一个大版本25.10中将对RISC-V处理器的准入门槛设置从此前的RVA20配置文件更新至最新...

取消回复欢迎 发表评论: