spring框架如何调用异步方法?Spring的TaskExecutor详解

一、基本使用

1、定义一个配置类

这里一个默认的线程池,一个起了自己名字的线程池。(可以配置多个线程池)

import org.springframework.aop.interceptor.AsyncExecutionAspectSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置、启用异步
 * 
 */
@EnableAsync(proxyTargetClass = true)
@Configuration
public class AsycTaskExecutorConfig {

	@Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME)
	public TaskExecutor taskExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		//核心线程池大小
		executor.setCorePoolSize(10);
		//最大线程数
		executor.setMaxPoolSize(20);
		//队列容量
		executor.setQueueCapacity(200);
		//活跃时间
		executor.setKeepAliveSeconds(60);
		//线程名字前缀
		executor.setThreadNamePrefix("taskExecutor-");
        // 等待所有任务结束后再关闭线程池
		executor.setWaitForTasksToCompleteOnShutdown(true);
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		return executor;
	}

	@Bean(name = "testEx")
	public TaskExecutor testEx() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		// 设置核心线程数
		executor.setCorePoolSize(5);
		// 设置最大线程数
		executor.setMaxPoolSize(10);
		// 设置队列容量
		executor.setQueueCapacity(20);
		// 设置线程活跃时间(秒)
		executor.setKeepAliveSeconds(60);
		// 设置默认线程名称
		executor.setThreadNamePrefix("test-");
		// 设置拒绝策略
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		// 等待所有任务结束后再关闭线程池
		executor.setWaitForTasksToCompleteOnShutdown(true);
		return executor;
	}
}

2、使用一下看看吧

    @RequestMapping("/testAnys")
    public void testAnys(){
        System.out.println("testAnys");
        testAnysMethod();
        System.out.println("1");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("2");
        testAnysMethod2();
        System.out.println("3");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    @Async//这个使用默认的线程池
    public void testAnysMethod(){
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("async");
    }
    @Async("testEx")//这个使用自己命名的线程池
    public void testAnysMethod2(){
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("testEx");
    }

// 注意返回值
@Async
Future<String> returnSomething(int i) {
	// this will be run asynchronously
}

3、运行

testAnys
async
1
2
testEx
3

二、拓展使用

1、使用包装TaskDecorator(拦截器)

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.core.task.TaskDecorator;

public class LoggingTaskDecorator implements TaskDecorator {

	private static final Log logger = LogFactory.getLog(LoggingTaskDecorator.class);

	@Override
	public Runnable decorate(Runnable runnable) {
		return () -> {
			logger.debug("Before execution of " + runnable);
			runnable.run();
			logger.debug("After execution of " + runnable);
		};
	}
}
@Bean
ThreadPoolTaskExecutor decoratedTaskExecutor() {
	ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
	taskExecutor.setTaskDecorator(new LoggingTaskDecorator());
	return taskExecutor;
}

org.springframework.core.task.support.CompositeTaskDecorator可用于顺序执行多个装饰器。

2、使用AsyncConfigurer

这种方式也可以配置默认的线程池

import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //获取逻辑CPU个数
        int poolSize = Runtime.getRuntime().availableProcessors();
        //设置核心线程数
        taskExecutor.setCorePoolSize(poolSize);
        //设置最大线程数
        taskExecutor.setMaxPoolSize(2 * poolSize);
        //线程池所使用的缓冲队列(线程池的最大线程数用完后,等待开启线程的数量)
        taskExecutor.setQueueCapacity(20 * poolSize);
        // 初始化线程
        taskExecutor.initialize();
        return taskExecutor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return null;
    }
}

3、异常处理

// 默认情况下,只记录异常。您可以定义一个自定义AsyncUncaughtExceptionHandler通过使用AsyncConfigurer或者<task:annotation-driven/>XML元素。
public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {

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

4、动态参数修改

(1)代码示例

package com.demo.springbootdemo.test;

import com.demo.springbootdemo.TestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.annotation.PostConstruct;
import java.util.concurrent.ThreadPoolExecutor;


/**
 * 基于apollo动态配置的线程池
 */
@Configuration
public class ThreadPoolConfig {

    private static Logger log = LoggerFactory.getLogger(ThreadPoolConfig.class);

    // 线程池存储
    private volatile ThreadPoolTaskExecutor EXECUTOR;

    private Integer corePoolSize;
    private Integer maxPoolSize;
    private Integer queueCapacity;
    private Integer keepAliveSeconds;

    @Value("${pool.corePoolSize:10}")
    public void setCorePoolSize(Integer corePoolSize) {
        this.corePoolSize = corePoolSize;
        ThreadPoolTaskExecutor threadPoolTaskExecutor = getThreadPoolTaskExecutor();
        if (threadPoolTaskExecutor == null) {
            return;
        }
        log.info("thread pool update corePoolSize:{},old corePoolSize:{}", corePoolSize, threadPoolTaskExecutor.getCorePoolSize());
        threadPoolTaskExecutor.setCorePoolSize(corePoolSize);
    }

    @Value("${pool.maxPoolSize:20}")
    public void setMaxPoolSize(Integer maxPoolSize) {
        this.maxPoolSize = maxPoolSize;
        ThreadPoolTaskExecutor threadPoolTaskExecutor = getThreadPoolTaskExecutor();
        if (threadPoolTaskExecutor == null) {
            return;
        }
        log.info("thread pool update maxPoolSize:{},old maxPoolSize:{}", maxPoolSize, threadPoolTaskExecutor.getMaxPoolSize());
        threadPoolTaskExecutor.setMaxPoolSize(maxPoolSize);
    }

    /**
     * 注意,队列大小无法动态配置,需要重建线程池
     */
    @Value("${pool.queueCapacity:100}")
    public void setQueueCapacity(Integer queueCapacity) {
        this.queueCapacity = queueCapacity;
        ThreadPoolTaskExecutor threadPoolTaskExecutor = getThreadPoolTaskExecutor();
        if (threadPoolTaskExecutor == null) {
            return;
        }
        // threadPoolTaskExecutor.setQueueCapacity(queueCapacity);
        // 重建
        recreateThreadPoolTaskExecutor();
    }

    @Value("${pool.keepAliveSeconds:60}")
    public void setKeepAliveSeconds(Integer keepAliveSeconds) {
        this.keepAliveSeconds = keepAliveSeconds;
        ThreadPoolTaskExecutor threadPoolTaskExecutor = getThreadPoolTaskExecutor();
        if (threadPoolTaskExecutor == null) {
            return;
        }
        log.info("thread pool update keepAliveSeconds:{},old keepAliveSeconds:{}", keepAliveSeconds, threadPoolTaskExecutor.getKeepAliveSeconds());
        threadPoolTaskExecutor.setKeepAliveSeconds(keepAliveSeconds);
    }

    private ThreadPoolTaskExecutor createThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setThreadNamePrefix("thread-pool-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    /**
     * 初始化
     */
    @PostConstruct
    public void initThreadPoolTaskExecutor() {
        EXECUTOR = createThreadPoolTaskExecutor();
    }

    /**
     * 只能通过此方法获取线程池
     */
    public ThreadPoolTaskExecutor getThreadPoolTaskExecutor() {
        return EXECUTOR;
    }

    /**
     * 重建线程池,注意线程安全
     */
    public void recreateThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor newThreadPoolExecutor = createThreadPoolTaskExecutor();
        ThreadPoolTaskExecutor oldThreadPoolExecutor = EXECUTOR;
        EXECUTOR = newThreadPoolExecutor;
        log.info("thread pool recreate success,new thread pool:{}", newThreadPoolExecutor);
        // 关闭旧线程池(温和关闭,等待任务完成)
        if (oldThreadPoolExecutor != null) {
            oldThreadPoolExecutor.shutdown();
        }
    }

}

(2)注意事项

1、最大线程数必须大于或等于核心线程数

2、支持动态修改的参数:corePoolSize、maxPoolSize、keepAliveSeconds 可通过setter方法动态修改,立即生效。

3、不支持动态修改的参数:queueCapacity(队列容量)、threadNamePrefix(线程名称前缀)等需要重建线程池才能生效,通常需重启应用。

4、配置中心集成:若使用 Nacos/Apollo,修改配置后会自动触发RefreshEvent,无需额外操作。

5、线程安全:ThreadPoolTaskExecutor的setter方法是线程安全的,可放心在运行时调用。

(3)支持动态修改的核心参数

这些参数修改后会立即生效,无需重启线程池:

  1. 核心线程数(corePoolSize)

    • 通过 setCorePoolSize(int) 方法修改。
    • 作用:调整线程池长期维持的核心线程数量。
    • 注意:若当前线程数超过新的核心线程数,多余的空闲核心线程会在下次检查时被销毁(受 keepAliveTime 影响)。
  2. 最大线程数(maxPoolSize)

    • 通过 setMaxPoolSize(int) 方法修改。
    • 作用:调整线程池允许创建的最大线程数量。
    • 注意:若新值小于当前线程数,超出的线程会在空闲时被销毁,但不会强制终止正在执行的任务。
  3. 空闲线程存活时间(keepAliveTime)

    • 通过 setKeepAliveSeconds(int) 方法修改(ThreadPoolTaskExecutor 提供的便捷方法,底层对应 ThreadPoolExecutor 的 setKeepAliveTime(long, TimeUnit))。
    • 作用:调整非核心线程(若 allowCoreThreadTimeOut 为 true,则包括核心线程)的空闲存活时间。
    • 注意:修改后对新创建的线程和现有空闲线程均生效。
  4. 核心线程是否允许超时(allowCoreThreadTimeOut)

    • 通过 setAllowCoreThreadTimeOut(boolean) 方法修改。
    • 作用:设置核心线程在空闲时是否遵循 keepAliveTime 超时策略(默认 false,核心线程永不超时)。
    • 注意:修改为 true 后,核心线程会在空闲超时后被销毁,可能导致下次任务需要重新创建线程。
  5. 拒绝策略(rejectedExecutionHandler)

    • 通过 setRejectedExecutionHandler(RejectedExecutionHandler) 方法修改。
    • 作用:调整线程池饱和时(线程满 + 队列满)的任务拒绝策略(如丢弃、抛异常、由提交者执行等)。
    • 注意:修改后对新提交的任务立即生效,不影响已在队列中等待的任务。

(4)不支持动态修改的参数

这些参数一旦线程池初始化后无法修改,若需调整必须销毁旧线程池并重建:

  1. 任务队列(queueCapacity)

    • 队列容量由初始化时的队列实现(如 LinkedBlockingQueue)决定,没有 setter 方法修改容量。
    • 原因:JDK 阻塞队列的容量通常是固定的(或需特殊实现),且线程池内部依赖队列的状态管理,动态修改可能导致并发问题。
  2. 线程名称前缀(threadNamePrefix)

    • 线程名称前缀在创建线程工厂时确定,线程创建后名称不可修改。
    • 原因:线程名称是线程的标识属性,一旦线程启动,名称无法变更,新线程会使用新前缀,但旧线程名称保持不变。
  3. 线程工厂(threadFactory)

    • 线程工厂用于创建新线程,没有 setter 方法修改,且修改后对已创建的线程无影响。
  4. 是否等待任务完成后关闭(waitForTasksToCompleteOnShutdown)

    • 属于线程池关闭时的行为参数,虽然有 setter 方法,但通常在初始化时确定,运行时修改意义不大。
  5. 关闭前的最大等待时间(awaitTerminationMillis)

    • 同上述关闭行为参数,运行时修改对已启动的关闭流程无影响。

三、源码分析

springboot提供了自动配置,默认核心线程是8个,队列容量和最大线程数为 Integer.MAX_VALUE,线程空闲存活时间为 60 秒,并且允许核心线程超时。:

参考资料

https://docs.spring.io/spring-framework/reference/integration/scheduling.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃了也弱了。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值