0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

强烈反对使用Spring封装的多线程类

PLC工控专栏 来源:加密狗破解 作者:加密狗破解 2022-02-22 08:58 次阅读

在很久很久之前,我有一段痛苦的记忆。那种被故障所驱使的感觉,在我脑海里久久无法驱散。

原因无它,有小伙伴开启了线程池的暴力使用模式。没错,就是下面这篇文章。

夺命故障 ! 炸出了投资人!

我有必要简单的复述一下。其主要原因,就是开发人员,在每一次方法调用里,都创建了一个单独的线程池去处理。这样的话,如果请求量一增加,整个操作系统的压力就会耗尽,最终所有的业务都无法响应。

强烈反对使用Spring封装的多线程类

我一直认为这是一个非常偶发的低级错误,发生频率非常的低。但随着这样的故障越来越多,xjjdog认识到这是一个普遍的现象。

以异步性能优化为目的,反而带来的整体业务不可用的结果,是非常打脸的一种优化。

1.Spring的异步代码

Spring作为Java届的杠把子框架,其过度封装的API深得开发人员的喜爱。根据语义化编程的逻辑,只要某些关键字在语言层面上过得去,我们就可以把它给加上去。比如@Async注解。

我永远想不通是什么给了开发人员勇气,去加上这个@Async注解,因为这种涉及到多线程的东西,即使是自己去创建线程,也是心怀敬畏,唯恐扰了操作系统的安宁。@Async这样的黑盒,真的可以那么顺畅的使用么?

我们不妨debug一下代码,让子弹飞一会儿。

首先,生成一个小小的项目,然后在主类上加上必须的注解。嗯,别忘了这一环,否则你后面加的注解将没什么用处。

@SpringBootApplication
@EnableAsync
publicclassDemoApplication{

复制代码

创造一个带@Async注解的方法。

@Component
publicclassAsyncService{
@Async
publicvoidasync(){
try{
Thread.sleep(1000);
System.out.println(Thread.currentThread());
}catch(Exceptionex){
ex.printStackTrace();
}
}
}

复制代码

然后,做一个对应的test接口,访问时会调用这个async方法。

@ResponseBody
@GetMapping("test")
publicvoidtest(){
service.async();
}

复制代码

访问时,直接打个断点,即可获取执行异步线程的线程池。

强烈反对使用Spring封装的多线程类

可以看到,异步任务使用了一个线程池,它的corePoolSize=8, 阻塞队列采用了无界队列LinkedBlockingQueue。一旦采用了这样的组合,最大线程数就会形同虚设,因为超出8个线程的任务,将全部会被放到无界队列里。使得下面的代码变成了摆设。

thrownewTaskRejectedException("Executor["+executor+"]didnotaccepttask:"+task,var4);

复制代码

如果你的访问量非常大,这些任务将全部堆积在LinkedBlockingQueue里。情况好一点的,这些任务的执行会变得延迟很大;情况坏一点的,任务太多将直接造成内存溢出OOM!

你可能会说,我可以自己指定另外一个ThreadPoolExceute,然后使用@Async注解来声明啊。说这话的同学,一定是能力比较强,或者Review的代码比较少,没有经过猪队友的洗礼。

2.是SpringBoot救了你

SpringBoot是个好东西。

在TaskExecutionAutoConfiguration中,通过生成ThreadPoolTaskExecutor的Bean,来提供默认的Executor。

@ConditionalOnMissingBean({Executor.class})
publicThreadPoolTaskExecutorapplicationTaskExecutor(TaskExecutorBuilderbuilder){
returnbuilder.build();
}

复制代码

也就是我们上面所说的那个。如果没有SpringBoot的助力,Spring将默认使用SimpleAsyncTaskExecutor。

参见org.springframework.aop.interceptor.AsyncExecutionInterceptor。

@Override
@Nullable
protectedExecutorgetDefaultExecutor(@NullableBeanFactorybeanFactory){
ExecutordefaultExecutor=super.getDefaultExecutor(beanFactory);
return(defaultExecutor!=null?defaultExecutor:newSimpleAsyncTaskExecutor());
}

复制代码

这就是Spring大仙所干的事。

SimpleAsyncTaskExecutor类设计的非常操蛋,因为它每执行一次,都会创建一个单独的线程,根本没有共用线程池。比如你的TPS是1000,异步执行了任务,那么你每秒将会生成1000个线程!

这明显是想要累死操作系统的节奏。

protectedvoiddoExecute(Runnabletask){
Threadthread=(this.threadFactory!=null?this.threadFactory.newThread(task):createThread(task));
thread.start();
}

复制代码

3.End

明眼人一看,这种使用new线程的处理方式将会是非常可怕的。但就拿Spring本身来说,引用SimpleAsyncTaskExecutor这个类的地方还不少,包括比较流行的AsyncRestTemplate。

强烈反对使用Spring封装的多线程类

这暴露了很多风险,尤其是竟然在这些列表中看到了redis的身影。这个类的设计,使得任务的执行变的非常的不可控。

看这个API,我感觉Spring是进入了设计的魔怔状态。

这个东西的隐藏bug可能还会更深!比如org.springframework.context.event.EventListener注解,用于实现DDD那套所谓的事件驱动模式,有不少框架直接set了AsyncRestTemplate,那么就等死吧。

赶紧把SimpleAsyncTaskExecutor加入你的API黑名单,或者埋坑清单吧!

创建线程有那么难么?需要使用Spring创建的线程?有时候我实在是想不通,暴露出这样的接口目的是为了什么。

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 封装
    +关注

    关注

    126

    文章

    7863

    浏览量

    142874
  • 多线程
    +关注

    关注

    0

    文章

    278

    浏览量

    19940
  • spring
    +关注

    关注

    0

    文章

    340

    浏览量

    14334
收藏 人收藏

    评论

    相关推荐

    socket 多线程编程实现方法

    在现代网络编程中,多线程技术被广泛应用于提高服务器的并发处理能力。Socket编程是网络通信的基础,而将多线程技术应用于Socket编程,可以显著提升服务器的性能。 多线程编程的基本概念 多线
    的头像 发表于 11-12 14:16 330次阅读

    Python中多线程和多进程的区别

    Python作为一种高级编程语言,提供了多种并发编程的方式,其中多线程与多进程是最常见的两种方式之一。在本文中,我们将探讨Python中多线程与多进程的概念、区别以及如何使用线程池与进程池来提高并发执行效率。
    的头像 发表于 10-23 11:48 383次阅读
    Python中<b class='flag-5'>多线程</b>和多进程的区别

    LWIP多线程强烈建议开启LWIP_ASSERT_CORE_LOCKED宏,这个在RTT里面要怎么实现?

    LWIP多线程强烈建议开启LWIP_ASSERT_CORE_LOCKED宏,这个在RTT里面要怎么实现,之前参考网上代码,这样写,压力测试下有概率断言失败 extern sys_mutex_t
    发表于 07-25 06:27

    ESP32会不会有多线程问题,需要加锁吗?

    ESP32会不会有多线程问题,需要加锁吗
    发表于 07-19 08:05

    多线程设计模式到对 CompletableFuture 的应用

    最近在开发 延保服务 频道页时,为了提高查询效率,使用到了多线程技术。为了对多线程方案设计有更加充分的了解,在业余时间读完了《图解 Java 多线程设计模式》这本书,觉得收获良多。本篇文章将介绍其中
    的头像 发表于 06-26 14:18 331次阅读
    从<b class='flag-5'>多线程</b>设计模式到对 CompletableFuture 的应用

    bootloader开多线程做引导程序,跳app初始化后直接进hardfualt,为什么?

    如标题,想做一个远程升级的项目,bootloader引导区域和app都是开多线程跑的,就是自己写了个小的任务调度器,没什么功能主要是想让程序快速的响应,延时不会对其他程序造成堵塞,程序测试
    发表于 04-18 06:07

    鸿蒙OS开发实例:【ArkTS多线程CPU密集型任务TaskPool】

    CPU密集型任务是指需要占用系统资源处理大量计算能力的任务,需要长时间运行,这段时间会阻塞线程其它事件的处理,不适宜放在主线程进行。例如图像处理、视频编码、数据分析等。 基于多线程并发机制处理CPU密集型任务可以提高CPU
    的头像 发表于 04-01 22:25 827次阅读
    鸿蒙OS开发实例:【ArkTS<b class='flag-5'>类</b>库<b class='flag-5'>多线程</b>CPU密集型任务TaskPool】

    鸿蒙OS开发实例:【ArkTS多线程I/O密集型任务开发】

    使用异步并发可以解决单次I/O任务阻塞的问题,但是如果遇到I/O密集型任务,同样会阻塞线程中其它任务的执行,这时需要使用多线程并发能力来进行解决。 I/O密集型任务的性能重点通常不在于CPU
    的头像 发表于 04-01 16:32 505次阅读
    鸿蒙OS开发实例:【ArkTS<b class='flag-5'>类</b>库<b class='flag-5'>多线程</b>I/O密集型任务开发】

    鸿蒙APP开发:【ArkTS多线程】TaskPool和Worker的对比

    TaskPool(任务池)和Worker的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以有效地避免这些任务阻塞主线程,从而最大化系统的利用率,降低整体资源消耗,并提高系统的整体性能。
    的头像 发表于 03-26 22:09 606次阅读
    鸿蒙APP开发:【ArkTS<b class='flag-5'>类</b>库<b class='flag-5'>多线程</b>】TaskPool和Worker的对比

    鸿蒙原生应用开发-ArkTS语言基础多线程TaskPool和Worker的对比(二)

    TaskPool运作机制 图1 TaskPool运作机制示意图 TaskPool支持开发者在主线程封装任务抛给任务队列,系统选择合适的工作线程,进行任务的分发及执行,再将结果返回给主线程
    发表于 03-26 15:25

    鸿蒙原生应用开发-ArkTS语言基础多线程TaskPool和Worker的对比(一)

    TaskPool(任务池)和Worker的作用是为应用程序提供一个多线程的运行环境,用于处理耗时的计算任务或其他密集型任务。可以有效地避免这些任务阻塞主线程,从而最大化系统的利用率,降低整体资源消耗
    发表于 03-25 14:11

    鸿蒙原生应用开发-ArkTS语言基础多线程I/O密集型任务开发

    使用异步并发可以解决单次I/O任务阻塞的问题,但是如果遇到I/O密集型任务,同样会阻塞线程中其它任务的执行,这时需要使用多线程并发能力来进行解决。 I/O密集型任务的性能重点通常不在于CPU的处理
    发表于 03-21 14:57

    java实现多线程的几种方式

    了多种实现多线程的方式,本文将详细介绍以下几种方式: 1.继承Thread 2.实现Runnable接口 3.Callable和Future 4.线程池 5.Java 8中
    的头像 发表于 03-14 16:55 676次阅读

    AT socket可以多线程调用吗?

    请问AT socket 可以多线程调用吗? 有互锁机制吗,还是要自己做互锁。
    发表于 03-01 08:22

    使用C++11新特性实现一个通用的线程池设计

    在C++11标准之前,多线程编程只能使用pthread_xxx开头的一组POSIX标准的接口。从C++11标准开始,多线程相关接口封装在了C++的std命名空间里。
    的头像 发表于 12-22 13:58 1257次阅读
    使用C++11新特性实现一个通用的<b class='flag-5'>线程</b>池设计