Spring定时任务的fixedRate和fixedDelay两个属性以及开启多线程与线程池设置

1、定时注解以及多线程

1.1、定时注解

这里首先解释定时任务@Scheduled的两个属性fixedRate和fixedDelay,
对于fixedDelay这个注解,就是等任务结束再开始计时,例如设置fixedDelay=5000,该方法执行需要2秒,那么再次执行的时间就是2秒+5秒=7秒,即在7秒后再次执行该任务。
对于fixedRate注解我有个误解,例如设置fixedDelay=5000,我以为每隔5秒就会调用该方法,其实不是,它根据上次任务开始的时候计时的。设置了fiexdRate=5000,该执行该方法所花的时间是2秒,那么3秒后就会再次执行该方法。
需要注意的是,如果该方法需要10分钟才执行完,5秒钟时候Spring就会再次调用这个任务,可是发现原来的任务还在执行,这个时候后续调用就阻塞了。
这里的解释来源于理解Spring定时任务@Scheduled的两个属性fixedRate和fixedDelay这篇文章。

因此这里需要增加注解来开启多线程,避免任务被阻塞:
在类上加@EnableAsync,在方法上加@Async注解来开启多线程模式,当到了下一次任务的执行时机时,如果之间任务还没执行完就会自动创建一个新的线程来执行它。这就符合我最初的误解了,即每5秒该任务就会由不同线程来调用一次

1.2、多线程

现在进行测试:创建定时任务,设置每10秒执行一次,但是该任务执行需要50秒

@Component
@EnableAsync
public class TestTask {

    @Scheduled(fixedRate = 10000)
    @Async
    public void executeTask() {
        // 任务内容
        long startTime = System.currentTimeMillis();

        System.out.println(Thread.currentThread().getName()+"任务开始执行: " + startTime);

        // 假设任务执行需要5分钟
        try {
            Thread.sleep(50000); // 模拟任务执行时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("任务被中断: " + e.getMessage());
        }

        long endTime = System.currentTimeMillis();
        System.out.println("任务执行结束: " + endTime + ", 耗时: " + (endTime - startTime) + "毫秒");
    }
}

在这里插入图片描述
差不多每10秒就会调用一次任务,基本符合预期,

1.3 线程池

在 Spring 框架中,通过 @EnableAsync@Async 注解开启多线程功能时,线程的管理是由 TaskExecutor(任务执行器) 控制的。默认情况下,Spring 会使用 SimpleAsyncTaskExecutor,它是一个线程池执行器,但 并不是一个真正的线程池。每次提交任务,它都会创建一个新线程,执行完后销毁线程。这就会导致资源的消耗,
比较浪费资源,这里上线程池操作:
新建线程池配置:

@Configuration
public class ScheduledPool {

    @Bean(name = "taskExecutor")
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        // 设置定时任务线程数量
        taskScheduler.setPoolSize(50);
        taskScheduler.setThreadNamePrefix("Custom-********");
        return taskScheduler;
    }
}

这里需要注意的是,Bean后面需要注明taskExecutor,因为 Spring 自动会使用 taskExecutor(名字为 taskExecutor 的 Bean)作为全局异步执行器。如果需要多个线程池,可以在 @Async 注解中自定义 Executor 名字:

@Async("taskExecutorOne")
public void asyncMethod() {
    // 你的异步逻辑
}

可以看到线程的前缀已经变成Custom-********,说明使用的正是线程池里面的线程
在这里插入图片描述
这里基本应用已经完成,下面是需要注意的事项和一些原理:

2、注意事项

2.1 线程池区别

@Async@Scheduled 的线程池独立性
@Scheduled 默认使用 TaskScheduler 线程池。
@Async 默认使用 TaskExecutor 线程池,
这里详细解释两个线程池的区别:
TaskSchedulerTaskExecutor 是 Spring 框架中两个常用的接口,用于管理线程池和异步任务处理。它们的主要区别在于设计目的、功能和使用场景。

2.2. TaskScheduler
用途: 用于处理定时任务和调度任务。
核心功能: 提供任务调度功能,可以按照固定的时间间隔或指定的时间点执行任务。
实现类:通常由 ThreadPoolTaskScheduler 实现,它结合了线程池的能力和调度功能。
使用场景:定时任务的调度,例如每隔一定时间执行任务或在某个具体时间点执行任务。

使用示例:

@Configuration
public class SchedulerConfig {

    @Bean
    public TaskScheduler taskScheduler() {
        ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
        scheduler.setPoolSize(5);
        scheduler.setThreadNamePrefix("Scheduled-Task-");
        return scheduler;
    }
}
@Component
public class ScheduledTask {

    @Autowired
    private TaskScheduler taskScheduler;

    public void startScheduledTask() {
        taskScheduler.scheduleAtFixedRate(() -> {
            System.out.println("Executing scheduled task at " + new Date());
        }, 5000); // 每隔 5 秒执行一次
    }
}

2.3 TaskExecutor
用途: 用于处理异步任务的执行。
核心功能: 是 Spring 的线程池抽象,旨在替代 java.util.concurrent.Executor,用于执行普通的并发任务。
实现类:通常由 ThreadPoolTaskExecutor 实现。还可以使用其他 Executor 实现,例如 SimpleAsyncTaskExecutor、SyncTaskExecutor 等。
使用场景:并发任务的异步执行,例如通过 @Async 注解支持的方法调用。

使用示例:

@Configuration
public class ExecutorConfig {

    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("Async-Task-");
        executor.initialize();
        return executor;
    }
}
@Component
public class AsyncTask {

    @Autowired
    private TaskExecutor taskExecutor;

    public void executeAsyncTask() {
        taskExecutor.execute(() -> {
            System.out.println("Executing async task on thread: " + Thread.currentThread().getName());
        });
    }
}

对比

特性TaskSchedulerTaskScheduler
设计目的定时任务调度异步任务执行
接口继承不继承 Executor继承 Executor
典型实现ThreadPoolTaskSchedulerThreadPoolTaskExecutor
任务类型定时任务(周期性、单次、触发器)普通异步任务
主要方法schedule、scheduleAtFixedRateexecute、submit
适用场景周期性任务、定时任务异步任务处理,方法异步调用

3、异步原理以及失效情况

3.1 异步原理
@Async的原理是通过 Spring AOP 动态代理 的方式来实现的。
在线程调用@Async注解标注的方法时,会调用代理,执行切入点处理器invoke方法,将方法的执行提交给线程池中的另外一个线程来处理,从而实现异步执行。
所以,相同类中的方法调用带@Async的方法是无法异步的,这种情况仍然是同步。

3.2 异步失效情况情况如下:

1、 未使用@EnableAsync注解
在Spring中要开启@Async注解异步的功能,需要在项目的启动类,或者配置类上,或者调用方法的类上使用@EnableAsync注解。

2 内部方法调用

在一个方法中调用另外一个方法会失效。例如:

@Slf4j
@Service
public class UserService {

    public void test() {
        async("test");
    }

    @Async
    public void async(String value) {
        log.info("async:{}", value);
    }
}

3 方法非public

因为private修饰的方法,只能在UserService类的对象中使用。
而@Async注解的异步功能,需要使用Spring的AOP生成UserService类的代理对象,该代理对象没法访问UserService类的private方法,因此会出现@Async注解失效的问题。

4、 方法返回值错误

想要使用@Async注解的异步功能,相关方法的返回值必须是void或者Future。

5 方法用static修饰了
使用@Async注解声明的方法,必须是能被重写的,很显然static修饰的方法,是类的静态方法,是不允许被重写的。
因此这种情况下,@Async注解的异步功能会失效。

6 方法用final修饰
在Java种final关键字,是一个非常特别的存在。
用final修饰的类,没法被继承。
用final修饰的方法,没法被重写。
用final修饰的变量,没法被修改。
如果final使用不当,也会导致@Async注解的异步功能失效

7 业务类没加@Service@controller@component等注解
业务类需要交给Spring管理,通过AOP生成代理类,因此需要加上以上注解。
在Java种final关键字,是一个非常特别的存在。
用final修饰的类,没法被继承。
用final修饰的方法,没法被重写。
用final修饰的变量,没法被修改。
如果final使用不当,也会导致@Async注解的异步功能失效

参考来源:
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:Spring的异步详解(@Async)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值