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

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

3天内不再提示

创建线程有那么难么?创建线程的大坑要避开

jf_ro2CN3Fa 来源:芋道源码 作者:芋道源码 2022-10-19 10:36 次阅读

1.Spring的异步代码

2.是SpringBoot救了你

3.End

e59430cc-4c39-11ed-a3b6-dac502259ad0.jpg

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

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

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

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

e5a5c77e-4c39-11ed-a3b6-dac502259ad0.png

我一直认为这是一个非常偶发的低级错误,发生频率非常的低。但随着这样的故障越来越多,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();
}

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

e5b246ca-4c39-11ed-a3b6-dac502259ad0.png

可以看到,异步任务使用了一个线程池,它的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。

e5c56796-4c39-11ed-a3b6-dac502259ad0.png

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

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

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

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

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

就连原生的线程池我们还没搞明白呢,你还给包了一层,这是方便我们甩锅啊!

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

    关注

    19

    文章

    2966

    浏览量

    104699
  • API
    API
    +关注

    关注

    2

    文章

    1498

    浏览量

    61954
  • 代码
    +关注

    关注

    30

    文章

    4779

    浏览量

    68516
  • spring
    +关注

    关注

    0

    文章

    340

    浏览量

    14334
  • 线程
    +关注

    关注

    0

    文章

    504

    浏览量

    19675

原文标题:新来了个技术总监:谁再用 @Async 创建线程以后就不用来了!!

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    跨平台的线程池组件--TP组件

    /销毁代价是很高的。那么我们怎么去设计多线程编程呢???答案:对于长驻的线程,我们可以创建独立的线程
    的头像 发表于 04-06 15:39 871次阅读

    fx3线程创建

    你好,如果我使用堆栈大小0x400(1024)使用Cyu3pthRead CueRead()API创建线程那么我可以使用Cyu3PDMACHNEL CURATE()API创建多少个通
    发表于 10-16 07:52

    如何在psoc cy8kit-059中创建线程

    你好,我想在PSoC CY8KIT-059中创建一个线程。我的Linux使用pthread创建一个线程但我不怎么在PSoC创建
    发表于 11-05 08:54

    初学RT-thread线程动态创建

    RT-thread初学线程动态创建线程静态创建线程钩子函数定时器获取系统时间动态创建定时器静态
    发表于 02-24 07:32

    python创建线程的两种方法

    ()可以看到输出hello Pythonhello MINGhello Pythonhello MING2. 用类创建线程相比较函数而言,使用类创建线程,会比较麻烦一点。首先,我们
    发表于 03-15 16:47

    请问大佬rt-thread smart用户程序可以创建实时线程?

    RT,rt-smart用户程序可以创建实时线程?现有例子看,感觉smart得内核是实时得,用户层程序是非实时得呢?如果用户可以创建实时线程
    发表于 04-15 09:54

    在RT-Thread系统中创建线程哪几种方式

    概述创建线程三要素:1.线程栈2.线程控制块3.线程主体函数在RTT中线程
    发表于 05-07 14:14

    RTThread创建线程时栈空间是如何进行分配的

    RTThread在动态创建线程时,其中一个参数是分配的线程堆栈大小。单个线程的堆栈大小可以在rtconfig文件中定义,那么总的堆栈空间是由
    发表于 09-21 10:57

    怎么解决创建多个线程和消息队列失败的问题呢?

    我使用的是STM32L010RB,在Main函数中创建了4个线程线程栈大小分别为256\\\\1024\\\\1024\\\\1024,结果在后面的线程中提示
    发表于 05-05 14:20

    linux下多线程创建与等待详解

    带来的问题回避进程严重得多。如果你的主线程,也就是main函数执行的那个线程,在你其他线程推出之前就已经退出,那么带来的bug则不可估量。
    发表于 04-02 14:48 320次阅读

    python创建线程的两种方法

    1. 用函数创建线程 在Python3中,Python提供了一个内置模块 threading.Thread ,可以很方便地让我们创建线程。 threading.Thread() 一
    的头像 发表于 03-15 16:47 5297次阅读

    python创建线程池的两种方法

    在使用多线程处理任务时也不是线程越多越好,由于在切换线程的时候,需要切换上下文环境,依然会造成cpu的大量开销。为解决这个问题,线程池的概念被提出来了。预先
    的头像 发表于 03-16 16:15 5974次阅读

    stm32裸机RT—thread开始创建线程详解

    线程系统中,每个线程都是独立的,互不干扰的,所以要为每个线程都分配独立的栈空间,这个栈空间通常是一个预先定义好的全局数组,也可以是动态分配的一段内存空间,但它们都存在于RAM中。        
    的头像 发表于 05-19 15:02 4986次阅读
    stm32裸机RT—thread开始<b class='flag-5'>创建</b><b class='flag-5'>线程</b>详解

    linux内核线程就这样诞生了

    线程是操作系统的重要组成部件之一,linux内核中,内核线程是如何创建的,在内核启动过程中,诞生了哪些支撑整个系统运转的线程,本文将带着这个疑问瞅一瞅内核源码,分析内核
    的头像 发表于 07-10 10:45 784次阅读
    linux内核<b class='flag-5'>线程</b>就这样诞生了<b class='flag-5'>么</b>?

    线程池的创建方式几种

    的开销。线程池的创建方式多种,下面将详细介绍几种常用的线程创建方式。 手动创建
    的头像 发表于 12-04 16:52 854次阅读