异步编程 - 08 Spring框架中的异步执行_TaskExecutor接口和@Async应用篇_asynctaskexecutor-程序员宅基地

技术标签: spring  TaskExecutor  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()时,该方法将在自定义的线程池中异步执行。

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

在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yangshangwei/article/details/132716078

智能推荐

如何配置DNS服务的正反向解析_dns反向解析-程序员宅基地

文章浏览阅读3k次,点赞3次,收藏13次。root@server ~]# vim /etc/named.rfc1912.zones #添加如下内容,也可直接更改模板。[root@server ~]# vim /etc/named.conf #打开主配置文件,将如下两处地方修改为。注意:ip地址必须反向书写,这里文件名需要和反向解析数据文件名相同。新建或者拷贝一份进行修改。nslookup命令。_dns反向解析

设置PWM占空比中TIM_SetCompare1,TIM_SetCompare2,TIM_SetCompare3,TIM_SetCompare4分别对应引脚和ADC通道对应引脚-程序员宅基地

文章浏览阅读2.5w次,点赞16次,收藏103次。这个函数TIM_SetCompare1,这个函数有四个,分别是TIM_SetCompare1,TIM_SetCompare2,TIM_SetCompare3,TIM_SetCompare4。位于CH1那一行的GPIO口使用TIM_SetCompare1这个函数,位于CH2那一行的GPIO口使用TIM_SetCompare2这个函数。使用stm32f103的除了tim6和tim7没有PWM..._tim_setcompare1

多线程_进程和线程,并发与并行,线程优先级,守护线程,实现线程的四种方式,线程周期;线程同步,线程中的锁,Lock类,死锁,生产者和消费者案例-程序员宅基地

文章浏览阅读950次,点赞33次,收藏19次。多线程_进程和线程,并发与并行,线程优先级,守护线程,实现线程的四种方式,线程周期;线程同步,线程中的锁,Lock类,死锁,生产者和消费者案例

在 Linux 系统的用户目录下安装 ifort 和 MKL 库并配置_在linux系统的用户目录下安装ifort和mkl库并配置-程序员宅基地

文章浏览阅读2.9k次。ifort 编译器的安装ifort 编译器可以在 intel 官网上下载。打开https://software.intel.com/content/www/us/en/develop/tools/oneapi/components/fortran-compiler.html#gs.7iqrsm点击网页中下方处的 Download, 选择 Intel Fortran Compiler Classic and Intel Fortran Compiler(Beta) 下方对应的版本。我选择的是 l_在linux系统的用户目录下安装ifort和mkl库并配置

使用ftl文件生成图片中图片展示无样式,不显示_ftl格式pdf的样式调整-程序员宅基地

文章浏览阅读689次,点赞7次,收藏8次。些项目时需要一个生成图片的方法,我在网上找到比较方便且适合我去设置一些样式的生成方式之一就是使用Freemarker,在对应位置上先写好一个html格式的ftl文件,在对应位置用${参数名}填写上。还记得当时为了解决图片大小设置不上,搜索了好久资料,不记得是在哪看到的需要在里面使用width与height直接设置,而我当时用style去设置,怎么都不对。找不到,自己测试链接,准备将所有含有中文的图片链接复制一份,在服务器上存储一份不带中文的文件。突然发现就算无中文,有的链接也是打不开的。_ftl格式pdf的样式调整

orin Ubuntu 20.04 配置 Realsense-ROS_opt/ros/noetic/lib/nodelet/nodelet: symbol lookup -程序员宅基地

文章浏览阅读1.5k次,点赞6次,收藏12次。拉取librealsense。_opt/ros/noetic/lib/nodelet/nodelet: symbol lookup error: /home/admin07/reals

随便推点

操作系统精选习题——第四章_系统抖动现象的发生由什么引起的-程序员宅基地

文章浏览阅读3.4k次,点赞3次,收藏29次。一.单选题二.填空题三.判断题一.单选题静态链接是在( )进行的。A、编译某段程序时B、装入某段程序时C、紧凑时D、装入程序之前Pentium处理器(32位)最大可寻址的虚拟存储器地址空间为( )。A、由内存的容量而定B、4GC、2GD、1G分页系统中,主存分配的单位是( )。A、字节B、物理块C、作业D、段在段页式存储管理中,当执行一段程序时,至少访问()次内存。A、1B、2C、3D、4在分段管理中,( )。A、以段为单位分配,每._系统抖动现象的发生由什么引起的

UG NX 12零件工程图基础_ug-nx工程图-程序员宅基地

文章浏览阅读2.4k次。在实际的工作生产中,零件的加工制造一般都需要二维工程图来辅助设计。UG NX 的工程图主要是为了满足二维出图需要。在绘制工程图时,需要先确定所绘制图形要表达的内容,然后根据需要并按照视图的选择原则,绘制工程图的主视图、其他视图以及某些特殊视图,最后标注图形的尺寸、技术说明等信息,即可完成工程图的绘制。1.视图选择原则工程图合理的表达方案要综合运用各种表达方法,清晰完整地表达出零件的结构形状,并便于看图。确定工程图表达方案的一般步骤如下:口分析零件结构形状由于零件的结构形状以及加工位置或工作位置的不._ug-nx工程图

智能制造数字化工厂智慧供应链大数据解决方案(PPT)-程序员宅基地

文章浏览阅读920次,点赞29次,收藏18次。原文《智能制造数字化工厂智慧供应链大数据解决方案》PPT格式主要从智能制造数字化工厂智慧供应链大数据解决方案框架图、销量预测+S&OP大数据解决方案、计划统筹大数据解决方案、订单履约大数据解决方案、库存周转大数据解决方案、采购及供应商管理大数据模块、智慧工厂大数据解决方案、设备管理大数据解决方案、质量管理大数据解决方案、仓储物流与网络优化大数据解决方案、供应链决策分析大数据解决方案进行建设。适用于售前项目汇报、项目规划、领导汇报。

网络编程socket accept函数的理解_当在函数 'main' 中调用 'open_socket_accept'时.line: 8. con-程序员宅基地

文章浏览阅读2w次,点赞38次,收藏102次。在服务器端,socket()返回的套接字用于监听(listen)和接受(accept)客户端的连接请求。这个套接字不能用于与客户端之间发送和接收数据。 accept()接受一个客户端的连接请求,并返回一个新的套接字。所谓“新的”就是说这个套接字与socket()返回的用于监听和接受客户端的连接请求的套接字不是同一个套接字。与本次接受的客户端的通信是通过在这个新的套接字上发送和接收数_当在函数 'main' 中调用 'open_socket_accept'时.line: 8. connection request fa

C#对象销毁_c# 销毁对象及其所有引用-程序员宅基地

文章浏览阅读4.3k次。对象销毁对象销毁的标准语法Close和Stop何时销毁对象销毁对象时清除字段对象销毁的标准语法Framework在销毁对象的逻辑方面遵循一套规则,这些规则并不限用于.NET Framework或C#语言;这些规则的目的是定义一套便于使用的协议。这些协议如下:一旦销毁,对象不可恢复。对象不能被再次激活,调用对象的方法或者属性抛出ObjectDisposedException异常重复地调用对象的Disposal方法会导致错误如果一个可销毁对象x 包含或包装或处理另外一个可销毁对象y,那么x的Disp_c# 销毁对象及其所有引用

笔记-中项/高项学习期间的错题笔记1_大型设备可靠性测试可否拆解为几个部分进行测试-程序员宅基地

文章浏览阅读1.1w次。这是记录,在中项、高项过程中的错题笔记;https://www.zenwu.site/post/2b6d.html1. 信息系统的规划工具在制订计划时,可以利用PERT图和甘特图;访谈时,可以应用各种调查表和调查提纲;在确定各部门、各层管理人员的需求,梳理流程时,可以采用会谈和正式会议的方法。为把企业组织结构与企业过程联系起来,说明每个过程与组织的联系,指出过程决策人,可以采用建立过程/组织(Process/Organization,P/O)矩阵的方法。例如,一个简单的P/O矩阵示例,其中._大型设备可靠性测试可否拆解为几个部分进行测试