首页 > 其他分享 >@Async你知道多少?

@Async你知道多少?

时间:2024-09-30 14:12:19浏览次数:1  
标签:执行器 import 配置 springframework 线程 org Async 多少 知道

我们都知道@Async是一个异步注解,用于在线程池异步执行任务,但是你真的了解其原理吗?

先来一个demo:

1)controller

package com.zxh.controller;


import com.zxh.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("/api")
public class TestController{
    @Autowired
    private TestService testService;

    @GetMapping("/test")
    public void  test(){
        for (int i = 0; i < 100; i++) {
            testService.test(i);
        }
    }

}

2)service

package com.zxh.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class TestService {

    @Async
    public void test(Integer i) {
        if (i % 2 == 0) {
            log.info("是偶数");
        } else {
            log.info("是奇数");
        }
    }
}

3)在启动类添加注解,开启异步

 4)启动后访问http://localhost:8081/api/test,打印日志如下:

 那么这里我们几乎是0配置,就能实现异步,且这些线程名称都是以 "task-" 开头,使用的是默认的线程池配置。如果仔细看日志会发现,这些名称最多直到8。那么是否可以推测出线程池默认的核心线程数是8?那么最大线程数又是多少呢?队列最大长度?(疑问1)

我们带着疑问打开这个注解

在注解的注释上有一些关键信息(已通过箭头方式标记)

首先可以看出这个注解可用在类和方法上。用在类上时,等价于在类中的所有方法上添加该注解。

对于①,简单来说就是对于目标方法,入参支持任何类型,但是!其返回值类型只限于void和Future。既然限制了返回值只有两种,如果我返回String会是什么结果呢?那就来试一下

就此得出结论,如果异步方法的返回值不是void和Future,那么最终的返回值都是null。那么为什么会这样呢,这个疑问稍后在源码中继续寻找答案(疑问2)。

既然返回String时返回值是null,那么我就是需要此类型怎么办呢?那就来到了②,意思就是说如果需要特定的返回值,那么可以使用AsyncResult 对象封装一下。看到这个是不是一脸懵逼,啥意思?该怎么使用呢?且看改造后的代码

 其实说白了,就是通过AsyncResult 对象封装后返回Future,然后通过get()方法来获取参数。所以说,通过AsyncResult 封装后也是返回的Future类型,如果不按此规则返回,那么返回的值是null,有空指针分险!

 接下来看注解中的属性,只有一个参数value,根据③注释可以知道,就是指定使用哪个执行器来执行异步任务(即指定线程池的名称)。通过value()点进去后发现只有一个地方使用这个值,其他地方都是注释的引用

 方法类路径

org.springframework.scheduling.annotation.AnnotationAsyncExecutionInterceptor#getExecutorQualifier

进入后打断点进行调试(调用时如果没有进入断点则需要重启服务,目前我还不清楚为啥是这样,应该怎么解决?)

 进入这个断点时继续F8进行调试,会进入另一个类

org.springframework.aop.interceptor.AsyncExecutionAspectSupport#determineAsyncExecutor

 

执行逻辑是先根据传入的方法获取执行器,由于没有指定执行器,故执行器是null。下面的逻辑就清晰了

根据方法获取@Async的value值,也就是执行器bean的名称(即①)。如果传入的执行器名称不为空,则从spring容器中获取目标执行器(即②),反之则获取默认配置的执行器(即③)。

获取到执行器后,在executors中维护方法和线程池的对应关系,executors是map类型,defaultExecutor是函数式接口Supplier。对于执行器对象信息,后面再细说。

 继续F8调试,会进入另一个类

org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke

 

其中L38是用来用map中根据method获取线程名称,也就是上一步executors。

L42~L54的代码,主要是封装一个Callable 对象,其内部对于return大致一看是不是就分为两种,分别是L46和L54。仔细看其中的逻辑,在try中先获取我们异步方法的返回值类型,如果是Future类型,会返回其值,反之直接reutrn null。你以为到这里就完了?就可以解释为啥限制了void和Future?那你就特错特错了,其实这里的判断是针对任务设置的,听不懂?那没关系。先看下面的doSubmit()方法。

最终把Callable 对象放到doSubmit()方法中,而doSubmit()方法看字面意思是提交任务,实际上也是这么个意思,就是来执行任务的。

org.springframework.aop.interceptor.AsyncExecutionAspectSupport#doSubmit

 

 这里有四个分支,其中 ListenableFuture 和 CompletableFuture 都继承自 Future ,所以前三个分支就是用来判断是否是 Future 类型的。如果不是 Future 类型,则直接返回null。那么对于源码注释中 “返回值限制是void和Future” 的问题(疑问2)也就迎刃而解了。

现在再回来看(疑问1),默认配置的核心线程数究竟是多少呢?上面并没有对执行器对象进行说明,这里通过断点可以看出,核心线程数是8,最大线程数 Integer.MAX_VALUE。默认的执行器的bean名称是 "applicationTaskExecutor",线程名称前缀是 "task-",

 那么这个类,在哪里配置的呢?既然已经知道了beanName,那么就简单了,按下两次Shift进行搜索

 找到这个自动配置类后打断点,重启后会自动进入断点

 就是在这里自动注入的默认的配置,可以看出大部分配置都来自properties

org.springframework.boot.autoconfigure.task.TaskExecutionProperties

在这里首先看到了线程名称的前缀配置,其他属性从Pool对象中读取

 自此(疑问1)也解决了。

 既然上述默认值的配置都看完了,那么如何进行自定义配置线程池参数并指定线程池名称呢?这就又回到了 @Async 注解的 value 属性了,下面修改程序如下

也就是自定义了执行器的配置,将其交给Spring管理。在@Async上标注执行器的beanName。重启后打断点

此时会进入②而不是③,因为此时指定了线程池名称,执行器对象如下,已经变成我们自己配置的值。

 那么此时就又出现一个疑问:一个项目共用一个线程池配置按业务使用线程池配置更佳?

其实正常情况下,所有异步的操作共用一个线程池配置是没问题的,但是也不是一概而论。根据实际场景需要,不同的业务最好使用不同的线程池配置,这样不同的业务之间如果队列出现问题不会影响这个服务,降低了耦合性。

上述自定义线程池配置时,是把线程池的配置写在业务类中,在实际开发中是不符合规范的,故需要将其抽取处理,如下

package com.zxh.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;


/**
 * @Auther: zxh
 * @Date: 20240930
 * @Description: 配置线程池
 */
@Configuration
@EnableAsync
public class TaskExecutePool {
    //核心线程数
    private static final Integer CORE_POOL_SIZE = 20;
    //最大线程数
    private static final Integer MAX_POOL_SIZE = 20;
    //缓存队列容量
    private static final Integer QUEUE_CAPACITY = 200;
    //线程活跃时间(秒)
    private static final Integer KEEP_ALIVE = 60;
    //默认线程名称前缀
    private static final String THREAD_NAME_PREFIX = "MyExecutor-";


    @Bean("MyAsyncTaskExecutor")
    public Executor myTaskAsyncPool() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(CORE_POOL_SIZE);
        executor.setMaxPoolSize(MAX_POOL_SIZE);
        executor.setQueueCapacity(QUEUE_CAPACITY);
        executor.setKeepAliveSeconds(KEEP_ALIVE);
        executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
        //拒绝策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

}

其实我还发现一个很奇怪的线程,只要我自定义了线程池配置并注入到Spring,如果在使用@Async时不写value的值,那么此时也会使用我们自定义的配置。

 那么为什么是这样的呢?

 

参考:https://www.cnblogs.com/thisiswhy/p/15233243.html

 

标签:执行器,import,配置,springframework,线程,org,Async,多少,知道
From: https://www.cnblogs.com/zys2019/p/18438975

相关文章

  • HarmonyOs DevEco Studio小技巧24--异步编程(Promises、async/await)
    异步编程:调用后耗时,不阻塞代码继续执行,将来完成后,触发回调函数传递结果异步编程的范畴:网络请求(如使用 fetch 或 XMLHttpRequest 发送HTTP请求获取数据)。文件读写操作(读取或写入本地文件)。数据库操作(查询、插入、更新、删除数据)。定时器函数(如 setTimeout 和 setInt......
  • AI绘画爆款治愈系创作,你还不知道?Stable Diffusion 轻松复刻某书爆款动漫卡通治愈文案
    前言情感治愈类一直是受众群体很高非常火爆的赛道,老徐也关注到在某书平台上,漫画治愈类风格的内容也是非常的受欢迎。先来看看以下一些案例看看这几个账号内容的质量就可以看出该部分内容是很受欢迎的,结合平台的用户群体成分,老徐觉得这个是非常值得尝试的一个创作赛道。......
  • .NET常见的几种项目架构模式,你知道几种?(附带使用情况投票)
    .NET常见的几种项目架构模式,你知道几种?(附带使用情况投票) 思维导航前言三层架构MVC架构DDD分层架构整洁架构CQRS架构最后总结参考文章DotNetGuide技术社区前言项目架构模式在软件开发中扮演着至关重要的角色,它们为开发者提供了一套组织和管理代码的指导原则,以......
  • PbootCms导航菜单标签的这些小技巧你都知道吗?
    为了帮助新手更好地理解和使用PbootCMS模板中的标签,以下是一些常见问题及其解决方案。1.常用的导航标签<spanstyle="font-size:14px;">{pboot:nav}<ahref="[nav:link]">[nav:name]</a>{/pboot:nav}</span>控制参数*num=数量:非必填,用于控制输出的数量。*parent=......
  • 谷歌网站收录查询,你知道怎么查询谷歌网站的收录情况吗
    查询谷歌网站的收录情况,可以通过以下几种方法来实现:一、使用GoogleSearchConsole(谷歌搜索控制台)GoogleSearchConsole是谷歌提供的官方工具,用于监控和管理网站在谷歌搜索结果中的表现。以下是具体步骤:访问并登录:访问GoogleSearchConsole官网,并使用谷歌账户登录。添加并......
  • 谷歌收录批量查询,你知道怎么批量查询谷歌收录
    批量查询谷歌收录是一个涉及多个步骤和工具的过程,旨在帮助网站管理员或SEO专业人士快速了解多个网站或页面在谷歌搜索引擎中的收录情况。以下是一些常用的批量查询谷歌收录的方法和步骤:一、使用GoogleSearchConsole(谷歌搜索控制台)虽然GoogleSearchConsole本身不直接支持批......
  • c# async await详解
    asyncawait传染性async/await具有传染性其实指的是你需要把异步函数的结果包装在Task类型当中。之所以c#要加async的主要原因是之前的await不是关键字,老代码可能会把await作为变量名,为了兼容性才加了async明确标识函数是continuation。await不能省略,在语义上有违直觉,为什么T......
  • 了解法国游戏玩家:应该知道的关键见解
    随着中国开发商向全球市场扩张,了解不同地区游戏玩家的偏好和行为至关重要。法国拥有丰富的游戏文化,呈现了一个独特的市场,开发商必须考虑这些独特的功能才能取得成功。以下是中国开发者应该注意的法国游戏玩家的关键特征:偏好叙事驱动游戏与许多其他地区相比,法国游戏玩家以其......
  • 【21 ZR联赛集训 day10】不知道高到哪里去了
    【21ZR联赛集训day10】不知道高到哪里去了二分答案。设敌人的速度是\(1\),二分我的速度\(v\),我可以从\(C\)走到\(T\)当对于每个我到达的点\(u\),敌人无法比我先到达,即敌人到达\(u\)最短用时比我大。先求敌人到每个结点的最短路,然后对于二分的一个\(v\),从\(C\)开始搜......
  • DAB认证是什么,费用多少?
    DAB认证是一项广泛应用于数字广播领域的认证,由数字音频广播联盟(DAB)所制定。该认证旨在确保数字广播设备的质量和性能达到一定的标准,涵盖了接收机、发射机等多种类型的设备。通过DAB认证,产品可以获得认证标识,这不仅证明了产品符合相关标准,还提升了产品在市场上的竞争力,增强了消费者......