异步编程 - 08 Spring框架中的异步执行_TaskExecutor接口和@Async应用篇

news2024/9/23 15:29:26

文章目录

  • 概述
  • Spring中对TaskExecutor的抽象
  • Spring框架内置的TaskExecutor实现。
    • SimpleAsyncTaskExecutor
    • SyncTaskExecutor
    • ConcurrentTaskExecutor
    • SimpleThreadPoolTaskExecutor
    • ThreadPoolTaskExecutor
    • TimerTaskExecutor
    • 小结
  • 如何在Spring中使用异步执行
    • 使用TaskExecutor实现异步执行
  • 使用注解@Async实现异步执行
  • SpringBoot 中使用 @Async
    • 基本使用
    • @Async适应自定义线程池

在这里插入图片描述


概述

在Spring Framework中分别使用TaskExecutorTaskScheduler接口提供异步执行和任务调度的抽象。

这里我们着重了解基于TaskExecutor支撑的注解@Async是如何实现异步处理的。


Spring中对TaskExecutor的抽象

Spring 2.0版本中提供了一种新的处理执行器(executors)的抽象,即TaskExecutor接口。TaskExecutor接口 与java.util.concurrent.Executor是等价的,其只有一个接口。

public interface TaskExecutor {
    void execute(Runnable task);
}

该接口具有单个方法execute(Runnable task),该方法基于线程池的语义和配置接收要执行的任务。

在这里插入图片描述


Spring框架内置的TaskExecutor实现。

在这里插入图片描述

SimpleAsyncTaskExecutor

这种TaskExecutor接口的实现不会复用线程,对应每个请求会新创建一个对应的线程来执行。它支持的并发限制将阻止任何超出限制的调用,这个可以通过调用setConcurrencyLimit方法来限制并发数,默认是不限制并发数的。


SyncTaskExecutor

这种TaskExecutor接口的实现不会异步地执行提交的任务,而是会同步使用调用线程来执行,这种实现主要用于没有必要多线程进行处理的情况,比如在进行简单的单元测试时。


ConcurrentTaskExecutor

这种TaskExecutor接口的实现是对JDK5中的java.util.concurrent.Executor的一个包装,通过setConcurrentExecutor(Executor concurrentExecutor)接口可以设置一个JUC中的线程池到其内部来做适配。

还有一个替代方案ThreadPoolTaskExecutor,它通过bean属性的方式配置Executor线程池的属性。一般很少会用到Concurrent TaskExecutor,但如果ThreadPoolTaskExecutor不够健壮满足不了你的需求,那么ConcurrentTaskExecutor也是一种选择。


SimpleThreadPoolTaskExecutor

这个实现实际上是Quartz的SimpleThreadPool的子类,它监听Spring的生命周期回调。当你有一个可能需要Quartz和非Quartz组件共享的线程池时,通常会使用该实现。


ThreadPoolTaskExecutor

该实现只能在Java 5环境中使用,其也是该环境中最常用的实现。它公开了bean属性,用于配置java.util.concurrent.ThreadPoolExecutor并将其包装在TaskExecutor中。如果你需要一些高级的接口,例如ScheduledThreadPoolExecutor,建议使用Concurrent TaskExecutor。


TimerTaskExecutor

该实现使用单个java.util.Timer对象作为其内部异步线程来执行任务。它与SyncTaskExecutor的不同之处在于,该实现对所有提交的任务都在Timer内的单独线程中执行,尽管提交的多个任务的执行是顺序同步的。

小结

如上,Spring框架本身提供了很多TaskExecutor的实现,但是如果不符合你的需要,你可以通过实现TaskExecutor接口来定制自己的执行器。


如何在Spring中使用异步执行

使用TaskExecutor实现异步执行

在Spring中TaskExecutor的实现类是以JavaBeans的方式提供服务的,比如下面这个例子,我们通过xml方式向Spring容器中注入了TaskExecutor的实现者ThreadPoolTaskExecutor的实例。

   <bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <!--1. 核心线程个数-->
        <property name="corePoolSize" value="5" />
        <!--2.最大线程个数 -->
        <property name="maxPoolSize" value="10" />
        <!--3.超过核心线程个数的线程空闲多久被回收 -->
        <property name="keepAliveSeconds" value="60" />
        
        <!--4.缓存队列大小 -->
        <property name="queueCapacity" value="20" />
        <!--5.拒绝策略 -->
        <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRuns
Policy" />
        </property>
    </bean>

·如上代码我们向Spring容器中注入了一个ThreadPoolTaskExecutor处理器实例,其配置属性与Java并发包中的线程池ThreadPoolExecutor类似。

·其中代码1、2将处理器中核心线程个数设置为5,最大线程个数设置为10。

·代码3设置了线程池中非核心线程空闲60s后会被自动回收。

·代码4设置了线程池阻塞队列的大小为20。

·代码5设置了线程池的拒绝策略,这里设置为CallerRunsPolicy,意为当线程池中的队列满了,并且所有线程都在忙碌的时候,如果此时向处理器提交了新的任务,则新的任务不再是异步执行,而是使用调用线程来执行。

当我们向Spring容器中注入了TaskExecutor的实例后,我们就可以在Spring容器中使用它。

<bean id="asyncExecutorExample"
    class="com.jiaduo.async.AsyncProgram.AsyncExecutorExample">
    <property name="taskExecutor" ref="taskExecutor" />
</bean>

·如上代码通过xml方式向Spring容器注入了AsyncExecutorExample的实例,并且其属性taskExecutor注入了上面创建的名称为taskExecutor的执行器,下面我们看看AsyncExecutorExample的代码。

public class AsyncExecutorExample {
    private class MessagePrinterTask implements Runnable {

        private String message;

        public MessagePrinterTask(String message) {
            this.message = message;
        }

        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " " + message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public TaskExecutor getTaskExecutor() {
        return taskExecutor;
    }

    public void setTaskExecutor(TaskExecutor taskExecutor) {
        this.taskExecutor = taskExecutor;
    }

    // 线程池执行器
    private TaskExecutor taskExecutor;

    public void printMessages() {
        for (int i = 0; i < 6; i++) {
            taskExecutor.execute(new MessagePrinterTask("Message" + i));
        }
    }
}

上述代码的AsyncExecutorExample中有一个类型为TaskExecutor的属性,我们通过setter访问器注入了该属性,其有一个printMessages方法用来触发异步任务执行,这里的异步任务被封装为MessagePrinterTask,其在run方法内先休眠1s模拟任务执行,然后打印输出。

下面我们看看如何把上面的内容组成可执行的程序,首先需要把上面两个xml配置汇总到beans.xml里面,代码如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-2.5.xsd">
    <bean id="taskExecutor"
        class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        ...
    </bean>
    <bean id="asyncExecutorExample"
        class="com.jiaduo.async.AsyncProgram.AsyncExecutorExample">
        <property name="taskExecutor" ref="taskExecutor" />
    </bean>

</beans>

然后我们需要编写的测试代码如下所示。

public static void main(String arg[]) throws InterruptedException {
    // 1.创建容器上下文
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
            new String[] { "beans.xml" });

    // 2.获取AsyncExecutorExample实例并调用打印方法
    System.out.println(Thread.currentThread().getName() + " begin ");
    AsyncExecutorExample asyncExecutorExample = applicationContext.getBean(AsyncExecutorExample.class);
    asyncExecutorExample.printMessages();
    System.out.println(Thread.currentThread().getName() + " end ");
}   

·代码1使用ClassPathXmlApplicationContext创建了一个Spring容器上下文,并且以beans.xml作为容器中bean的元数据。

·代码2从容器上下文中获取AsyncExecutorExample的实例,并且调用了print-Messages方法。由于printMessages方法内的6个任务提交到了执行器线程进行处理,所以main函数所在线程调用printMessages方法后马上返回,然后具体任务是由执行器中的线程执行的。

·运行上面代码,一个可能的输出为:

main begin 
main end 
taskExecutor-1 Message0
taskExecutor-3 Message2
taskExecutor-2 Message1
taskExecutor-5 Message4
taskExecutor-4 Message3
taskExecutor-1 Message5

可知具体任务是在执行器线程中执行的,而不是在main函数所在线程中执行的。运行上面的代码后,虽然main函数所在线程会马上结束,并且异步任务也执行完了,但是JVM进程并没有退出,这是因为执行器ThreadPoolTaskExecutor中的线程都是用户线程而不是Deamon线程。而JVM退出的条件是进程中不含有任何用户线程,所以我们要与使用Java并发包中的线程池一样,需要显式关闭线程池。

为此我们在AsyncExecutorExample中添加shutdown方法:

public void shutdown() {
    if (taskExecutor instanceof ThreadPoolTaskExecutor) {
        ((ThreadPoolTaskExecutor) taskExecutor).shutdown();
    }
}

然后在测试类的main函数最后添加如下代码:

// 3.关闭执行器,释放线程
asyncExecutorExample.shutdown();

添加代码后,运行测试代码,输出如下所示。

main begin 
main end 
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at com.jiaduo.async.AsyncProgram.AsyncExecutorExample$MessagePrinterTask.run(AsyncExecutorExample.java:17)
...

如上可知我们的任务都被中断了(因为我们的任务中调用了sleep方法),这是因为默认情况下执行器ThreadPoolTaskExecutor中的变量waitForTasksToComplete OnShutdown为false,意为关闭执行器时不等待正在执行的任务执行完毕就中断执行任务的线程。所以我们需要修改ThreadPoolTaskExecutor注入的配置,代码如下所示。

<bean id="taskExecutor"
    class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    ...
    <property name="waitForTasksToCompleteOnShutdown"
        value="true"></property>
</bean>

如上配置在注入ThreadPoolTaskExecutor的配置属性最后添加了变量waitForTasksTo CompleteOnShutdown为true的配置,然后运行测试类,就会发现等异步任务执行完毕后,当前jvm进程就不存在了,这说明执行器已经被优雅地退出了。


使用注解@Async实现异步执行

在Spring中可以在方法上添加@Async注释,以便异步执行该方法。换句话说,调用线程将在调用含有@Async注释的方法时立即返回,并且该方法的实际执行将发生在Spring的TaskExecutor异步处理器线程中。需要注意的是,该注解@Async默认是不会解析的,你可以使用如下两种方式开启该注解的解析。

·基于xml配置Bean时需要加入如下配置,才可以开启异步处理:

   <task:annotation-driven  />

·在基于注解的情况下可以添加如下注解来启动异步处理:

   @EnableAsync

下面我们看看如何使用第一种方式开启并使用异步执行,首先我们需要在beans-annotation.xml中配置如下代码。

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-2.5.xsd
       http://www.springframework.org/schema/task
       http://www.springframework.org/schema/task/spring-task.xsd">
    <!--1.开启Async注解的解析 -->
    <task:annotation-driven />

    <!--2.注入业务Bean -->
    <bean id="asyncCommentExample"
        class="com.jiaduo.async.AsyncProgram.AsyncAnnotationExample">
    </bean>
</beans>

如上代码1通过配置开启了对注解Async的解析,代码2注入了我们的业务Bean,其代码如下所示。

public class AsyncAnnotationExample {
    @Async
    public void printMessages() {
        for (int i = 0; i < 6; i++) {
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " 、
Message" + i);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

如上代码的printMessages方法添加了@Async注解,方法内循环6次,循环中先让执行线程休眠1s,然后打印输出。

下面我们组合上面的代码片段形成一个可执行程序进行测试,测试代码如下所示。

public static void main(String arg[]) throws InterruptedException {
    // 1.创建容器上下文
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
            new String[] { "beans-annotation.xml" });

    // 2. 获取AsyncAnnotationExample实例并调用打印方法
    System.out.println(Thread.currentThread().getName() + " begin ");
    AsyncAnnotationExample asyncCommentExample = applicationContext.getBean(AsyncAnnotationExample.class);
    asyncCommentExample.printMessages();
    System.out.println(Thread.currentThread().getName() + " end ");
}

如上代码1使用beans-annotation.xml作为容器Bean的元数据创建了Spring上下文,代码2从中获取了AsyncAnnotationExample的实例,然后调用其printMessages,main线程调用该方法后,该方法会马上返回,printMessages内的任务是使用Spring框架内的默认执行器SimpleAsyncTaskExecutor中的线程来执行的。运行上面代码的一个可能的输出结果如下所示。

main begin 
main end 
SimpleAsyncTaskExecutor-1 Message0
SimpleAsyncTaskExecutor-1 Message1
SimpleAsyncTaskExecutor-1 Message2
SimpleAsyncTaskExecutor-1 Message3
SimpleAsyncTaskExecutor-1 Message4
SimpleAsyncTaskExecutor-1 Message5

可知具体执行异步任务的是SimpleAsyncTaskExecutor中的线程,而不是main函数所在线程。当然我们可以指定自己的执行器来执行我们的异步任务,这需要我们在xml配置自己的执行器,代码如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    ...
    <!--0.创建自己的业务线程池处理器 -->
    <task:executor id="myexecutor" pool-size="5" />
    <!--1.开启Async注解的解析 -->
    <task:annotation-driven executor="myexecutor"/>
    <!--2.注入业务Bean -->
    <bean id="asyncCommentExample"
        class="com.jiaduo.async.AsyncProgram.AsyncAnnotationExample">
    </bean>
</beans>

如上代码0为我们创建了自己的线程池处理器,代码1则把我们的线程池处理器作为异步任务的处理器,运行如上代码,可以看到一个可能的输出结果如下:

main begin 
main end 
myexecutor-1 Message0
myexecutor-1 Message1
myexecutor-1 Message2
myexecutor-1 Message3
myexecutor-1 Message4
myexecutor-1 Message5

由如上代码可知,异步任务是使用我们自己的线程池执行器执行的。

下面我们看看第二种方式是如何使用注解方式开启异步处理的,首先我们需要在xml里面进行如下配置。

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-2.5.xsd
       http://www.springframework.org/schema/task
       http://www.springframework.org/schema/task/spring-task.xsd">

    <!--1.扫描bean的包路径 -->
    <context:component-scan
        base-package="com.jiaduo.async.AsyncProgram" />
</beans

如上代码1配置了包扫描路径,框架会扫描该包下面含有@Component注解的从Bean到Spring的容器。

然后要在AsyncAnnotationExample类中加上如下注解。

@EnableAsync//开启异步执行
@Component//把该Bean注入Spring容器
public class AsyncAnnotationExample {
    @Async
    public void printMessages() {
        ...
    }
}

如上代码使用了注解@EnableAsync开启异步执行。

另外需要注意的是@Async注解本身也是有参数的,比如我们可以在某一个需要异步处理的方法上加@Async,注解时指定使用哪一个线程池处理器来进行异步处理。

@Async("bizExecutor")
void doSomething(String s) {
....
}

如上代码指定了方法doSomething使用名称为bizExecutor的线程池处理器来执行异步任务。

上面我们讲解的异步任务都是没有返回结果的,其实基于@Async注解的异步处理也是支持返回值的,但是返回值类型必须是Future或者其子类类型的,比如返回的Future类型可以是普通的java.util.concurrent.Future类型,也可以是Spring框架的org.springframework.util.concurrent.ListenableFuture类型,或者JDK8中的java.util.concurrent.CompletableFuture类型,又或者Spring中的AsyncResult类型等。这提供了异步执行的好处,以便调用者可以在调用Future上的get()之前处理其他任务。

如下代码展示了在AsyncAnnotationExample中,方法doSomething是如何在具有返回值的方法上使用注解@Async的。

@Async
public CompletableFuture<String> doSomething() {
    // 1.创建future
    CompletableFuture<String> result = new CompletableFuture<String>();
    // 2.模拟任务执行
    try {
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName() + "doSomething");
    } catch (Exception e) {
        e.printStackTrace();
    }
    result.complete("done");


    // 3.返回结果
    return result;
}

代码1创建了一个CompletableFuture类型的Future实例,代码2休眠5s模拟任务执行,然后设置Future的执行结果,代码3则返回Future对象。

下面修改我们的测试代码对其进行测试,代码如下所示。

public static void main(String arg[]) throws InterruptedException {
    // 1.创建容器上下文
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext(
            new String[] { "beans-annotation.xml" });

    // 2. 获取AsyncExecutorExample实例并调用打印方法
    System.out.println(Thread.currentThread().getName() + " begin ");
    AsyncAnnotationExample asyncCommentExample = applicationContext.getBean(AsyncAnnotationExample.class);

    // 3.获取异步future并设置回调
    CompletableFuture<String> resultFuture = asyncCommentExample.doSomething();
    resultFuture.whenComplete(new BiConsumer<String, Throwable>() {
        @Override
        public void accept(String t, Throwable u) {
            if (null == u) {
                System.out.println(Thread.currentThread().getName() + " " + t);
            } else {
                System.out.println("error:" + u.getLocalizedMessage());
            }

        }
    });

    System.out.println(Thread.currentThread().getName() + " end ");
}

代码3的main函数所在线程调用了AsyncAnnotationExample的doSomething方法,该方法会马上返回一个CompletableFuture,我们在其上设置了回调函数,之后main线程就退出了,最终doSomething方法内的代码就是使用处理器线程池中的线程来执行的,并当执行完毕后回调我们设置的回调函数。

运行上面代码的输出如下所示。

main begin 
main end 
SimpleAsyncTaskExecutor-1doSomething
SimpleAsyncTaskExecutor-1 done

如上代码可知,doSomething方法的执行是使用SimpleAsyncTaskExecutor线程池处理器来执行的,而不是main函数所在线程进行执行。

最后看看使用@Async注解遇到异常时该如何处理。当@Async方法具有Future类型返回值时,很容易管理在方法执行期间抛出的异常,因为会在调用get方法等待结果时抛出该异常。但是对于void返回类型来说,异常未被捕获且无法传输。这时候可以提供AsyncUncaughtExceptionHandler来处理该类异常。以下示例显示了如何执行该操作。

  public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // handle exception
    }
}

然后我们在xml里面配置即可:

    <task:annotation-driven
        exception-handler="myAsyncUncaughtExceptionHandler" />

    <bean id="myAsyncUncaughtExceptionHandler" 
class="com.artisan.async.AsyncProgram.MyAsyncUncaughtExceptionHandler"></bean>

如上代码的xml配置首先创建了实例myAsyncUncaughtExceptionHandler,然后将其设置到注解annotation-driven中,在异步任务中抛出异常时会在MyAsyncUncaught ExceptionHandler的handleUncaughtException方法中得到处理。

由上可知基于@Async注解实现异步执行的方式时,大大简化了我们异步编程的运算负担,我们不必再显式地创建线程池并把任务手动提交到线程池内,只要直接在需要异步执行的方法上添加@Async注解即可。当然,当我们需要使用自己的线程池来异步执行标注@Async的方法时,还是需要显式创建线程池的,但这时并不需要显式提交任务到线程池。


SpringBoot 中使用 @Async

使用 @Async 注解步骤:

  • 添加 @EnableAsync 注解。在主类上或者 某个类上,否则,异步方法不会生效
  • 添加 @Async 注解。在异步方法上添加此注解。异步方法不能被 static 修饰
  • 需要自定义线程池,则可以配置线程池

基本使用

在Spring Boot中,您可以使用@Async注解来实现异步方法调用。@Async注解允许您将一个方法标记为异步执行,这意味着方法的调用将立即返回,而不会等待方法的执行完成。

要在Spring Boot应用程序中使用@Async,请按照以下步骤进行操作:

  1. 添加依赖:首先,您需要确保您的Spring Boot项目具有适当的依赖项。确保您的pom.xml文件中包含spring-boot-starter-webspring-boot-starter-aop依赖,因为@Async依赖于AOP(面向切面编程)来实现异步执行。
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>
  1. 配置异步执行:在Spring Boot应用程序的主类上添加@EnableAsync注解,以启用异步执行。通常,主类是带有public static void main(String[] args)方法的类。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class YourApplication {

    public static void main(String[] args) {
        SpringApplication.run(YourApplication.class, args);
    }
}
  1. 创建异步方法:在您的服务类或任何其他组件中,使用@Async注解标记要异步执行的方法。
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @Async
    public void asyncMethod() {
        // 异步执行的代码
    }
}
  1. 调用异步方法:在需要异步执行的地方调用带有@Async注解的方法。
@Service
public class AnotherService {

    private final MyService myService;

    public AnotherService(MyService myService) {
        this.myService = myService;
    }

    public void someMethod() {
        // 同步代码

        // 调用异步方法
        myService.asyncMethod();

        // 继续执行其他同步代码
    }
}

现在,当调用myService.asyncMethod()时,该方法将在单独的线程中异步执行,而不会阻塞调用者线程。

请注意,要使@Async正常工作,您还需要配置一个TaskExecutor bean。Spring Boot提供了默认的SimpleAsyncTaskExecutor,但您也可以根据需要配置自定义的执行器。例如,您可以在application.propertiesapplication.yml中添加以下配置:

spring:
  task:
    execution:
      pool:
        core-size: 5

这个配置示例将创建一个具有5个核心线程的线程池来执行异步方法。您可以根据您的需求进行调整。

希望这可以帮助您在Spring Boot中使用@Async来实现异步方法调用。

@Async适应自定义线程池

@Async 底层原理:就是通过线程池创建一个线程,然后去执行业务逻辑。

@Async 注解会应用默认线程池 SimpleAsyncTaskExecutor

这种TaskExecutor接口的实现不会复用线程,对应每个请求会新创建一个对应的线程来执行。它支持的并发限制将阻止任何超出限制的调用,这个可以通过调用setConcurrencyLimit方法来限制并发数,默认是不限制并发数的。

@Async 默认异步配置使用的是 SimpleAsyncTaskExecutor,该线程池默认来一个任务创建一个线程,若系统中不断的创建线程,最终会导致系统占用内存过高,引发 OutOfMemoryError 错误。针对线程创建问题,SimpleAsyncTaskExecutor 提供了限流机制,通过 concurrencyLimit 属性来控制开关,当 concurrencyLimit>=0 时开启限流机制,默认关闭限流机制,即 concurrencyLimit=-1,当关闭情况下,会不断创建新的线程来处理任务。基于默认配置,SimpleAsyncTaskExecutor 并不是严格意义的线程池,达不到线程复用的功能

Spring允许您为异步方法配置不同的TaskExecutor,以便更好地控制异步任务的执行。

以下是如何在Spring Boot中配置自定义线程池并将其用于@Async方法的步骤:

  1. 创建一个自定义的TaskExecutor bean,以定义您的线程池配置。您可以在Spring的配置类(通常是带有@Configuration注解的类)中完成此操作。例如:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

@Configuration
public class CustomThreadPoolConfig {

    @Bean("customTaskExecutor")
    public TaskExecutor customTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10); // 设置核心线程数
        executor.setMaxPoolSize(20); // 设置最大线程数
        executor.setQueueCapacity(100); // 设置队列容量
        executor.setThreadNamePrefix("custom-async-"); // 设置线程名称前缀
        executor.initialize();
        return executor;
    }
}

上述代码创建了一个名为customTaskExecutor的自定义TaskExecutor bean,您可以根据需要调整线程池的各种参数。

  1. @Async注解中使用自定义的TaskExecutor bean名称。在需要异步执行的方法上,使用@Async注解并指定要使用的TaskExecutor bean的名称,如下所示:
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @Async("customTaskExecutor") // 使用自定义的TaskExecutor
    public void asyncMethod() {
        // 异步执行的代码
    }
}

这里,@Async注解中的value属性设置为您在第一步中定义的customTaskExecutor bean的名称。

  1. 现在,当调用myService.asyncMethod()时,该方法将在自定义的线程池中异步执行。

这样,您就可以轻松地配置和使用自定义线程池来管理异步任务的执行。这对于需要更多控制的复杂应用程序非常有用。确保根据您的需求调整线程池的大小和其他参数。

在这里插入图片描述

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

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

相关文章

喜讯 | 智安零信任安全项目入选信通院“安全守卫者计划”优秀案例

近日&#xff0c;中国信息通信研究院&#xff08;以下简称“中国信通院”&#xff09;主办的首届“SecGo云和软件安全大会”成功举办&#xff0c;会上重磅揭晓了“安全守卫者计划 零信任”优秀案例征集活动结果&#xff0c;深圳市智安网络有限公司与大庆油田信息技术公司联合申…

go小知识2

Golang开发新手常犯的50个错误_gezhonglei2007的博客-CSDN博客 一些题目整理&#xff0c;附带大佬的解释 1.go中哪些值不能寻址& 常量&#xff08;const常量&#xff0c;字面值3.14&#xff0c;字符串“xxx”&#xff0c;函数或方法, map的val值&#xff09; golang中接…

Win11怎么显示隐藏文件

为了保护电脑的安全&#xff0c;系统会将一些重要的文件或者文件夹隐藏起来&#xff0c;导致我们无法轻易的找到和打开&#xff0c;那么这些隐藏的文件怎么显示呢&#xff0c;下面小编就给大家带来Win11显示隐藏文件的方法&#xff0c;感兴趣的小伙伴快来和小编一起看看吧。 W…

第六章 图 二、图的存储结构(邻接矩阵法)

我们根据问题来引出邻接矩阵的各种含义&#xff1a; 目录 我们根据问题来引出邻接矩阵的各种含义&#xff1a; 1.如何计算指定顶点的度、入度、出度&#xff08;分无向图、有向图来考虑)&#xff1f;时间复杂度如何? 2.如何找到与顶点相邻的边&#xff08;入边、出边&…

laravel框架系列(一),Dcat Admin 安装

介绍 Laravel 是一个流行的 PHP 开发框架&#xff0c;它提供了一套简洁、优雅的语法和丰富的功能&#xff0c;用于快速构建高质量的 Web 应用程序。 以下是 Laravel 的一些主要特点和功能&#xff1a; MVC 架构&#xff1a;Laravel 使用经典的模型-视图-控制器&#xff08;MV…

LeetCode 92. Reverse Linked List II【链表,头插法】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

Git与IDEA: 解决`dev`分支切换问题及其背后原因 为何在IDEA中无法切换到`dev`分支?全面解析!

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Spring Boot工具篇--使用yml多环境配置和创建多环境profile打包

1、yml多环境配置 在Spring Boot中多环境配置文件名需要满足application-{profile}.yml的格式&#xff0c;其中{profile}对应你的环境标识; application-dev 开发环境 application-test 测试环境 application-prod 生产环境 如果我们要激活某一个环境&#xff0c;只需要在 ap…

【List篇】ArrayList 详解(含图示说明)

Java中的ArrayList是一个动态数组&#xff0c;可以自动扩展容量以适应数据的添加和删除。它可以用来存储各种类型的数据&#xff0c;例如String&#xff0c;Integer&#xff0c;Boolean等。ArrayList实现了List接口&#xff0c;可以进行常见的List操作&#xff0c;例如添加、插…

ping与Traceroute是如何工作的

ping 是基于 ICMP 协议工作的。ICMP 全称 Internet Control Message Protocol&#xff0c;就是互联网控制报文协议。 ICMP 报文是封装在 IP 包里面的。因为传输指令的时候&#xff0c;肯定需要源地址和目标地址。它本身非常简单。 ICMP 报文有很多的类型&#xff0c;不同的类型…

最近读书了吗?林曦老师与你分享来自暄桐课堂的读书方法

近来&#xff0c;大家有在开心读书吗&#xff1f;对于读书&#xff0c;有一个很生动的说法&#xff1a;“无事常读书&#xff0c;一日是四日。若活七十年&#xff0c;便二百八十。”读书帮助我们超越个体生命经验的限制&#xff0c;此时此地的我们&#xff0c;也可借由书本&…

JS-DOM BOM(未完)

一.Web APIs 1.Web APIs和JS基础关联性 1.1 JS组成 1.2 JS基础阶段以及Web APIs阶段 2.API和Web API 2.1 API 2.2 Web API 2.3 API和Web API总结 二.DOM 1.DOM简介 1.1 什么是DOM 1.2 DOM树 2.获取元素 2.1 如何获取页面元素 2.2 根据ID获取 使用getElementById()方法获…

爬虫系统的核心:如何创建高质量的HTML文件?

在网页抓取或爬虫系统中&#xff0c;HTML文件的创建是一项重要的任务。HTML文件是网页的基础&#xff0c;包含了网页的所有内容和结构。在爬虫系统中&#xff0c;我们需要生成一个HTML文件&#xff0c;以便于保存和处理网页的内容。 在这种情况下&#xff0c;可以使用Java函数…

deepfm内容理解

对于CTR问题&#xff0c;被证明的最有效的提升任务表现的策略是特征组合(Feature Interaction)&#xff1b; 两个问题&#xff1a; 如何更好地学习特征组合&#xff0c;进而更加精确地描述数据的特点&#xff1b; 如何更高效的学习特征组合。 DNN局限 &#xff1a;当我们使…

ESXI主机扩容(VCSA)

原因分析SCSI扩容 VMware为ESXI虚拟机硬盘扩容(需要先关闭ESXI) ESXI扩容前ESXI扩容 https://blog.csdn.net/tongxin_tongmeng/article/details/132652423 ESXI扩容后

2023年高教社杯数学建模国赛 赛题浅析

2023年国赛如期而至&#xff0c;为了方便大家尽快确定选题&#xff0c;这里将对赛题进行浅析&#xff0c;以分析赛题的主要难点、出题思路以及选择之后可能遇到的难点进行说明&#xff0c;方便大家尽快确定选题。 难度排序 B>A>C 选题人数 C>A>B (预估结果&…

软件工程课件

软件工程 考点概述软件工程概述能力成度模型能力成熟度模型集成软件过程模型逆向工程![ ](https://img-blog.csdnimg.cn/425cea8190fb4c5ab2bf7be5e2ad990e.png) 考点概述 重点章节 软件工程概述 之前老版教程的&#xff0c;之前考过 能力成度模型 记忆 能力等级 和 特点 能力…

【Java】基础练习 --- Stream练习

1.拼接 给定一个字符串数组,使用 Stream 把所有字符串拼接成一个字符串。 String[] arr {"a", "b", "c"}; 输出: abc&#xff08;1&#xff09;源码&#xff1a; package swp.kaifamiao.codes.Java.d0907;import java.util.stream.Stream;/*…

2023高教社杯全国大学生数学建模竞赛E题代码解析

2023高教社杯全国大学生数学建模竞赛E题 黄河水沙监测数据分析 代码解析 因为一些不可抗力&#xff0c;下面仅展示部分python代码&#xff08;第一问的部分&#xff09;&#xff0c;其余代码看文末 首先导入包&#xff1a; import numpy as np import pandas as pd import m…

【Python】matplotlib分格显示

参考&#xff1a;matplotlib图形整合之多个子图一起绘制_matplotlib多子图_王小王-123的博客-CSDN博客 方式一&#xff1a; import matplotlib.pyplot as plt import matplotlib.gridspec as gridspecplt.figure() # 方式一: gridspec # rowspan:行的跨度&#xff0c;colspan…