关于springboot下的定时任务(Schedule)

本文详细探讨了SpringBoot下的定时任务(Schedule),从相关准备开始,逐步分析了单个定时任务、多个任务串行和并行执行、异步任务等不同情况,通过实例展示了任务调度的原理和线程使用。同时,指出了并发执行时可能出现的问题及解决方案,包括自定义配置实现AsyncConfigurer接口。最后,讨论了同步任务与异步任务混合执行时的影响。
摘要由CSDN通过智能技术生成

在此之前推荐一篇关于Schedule的源码解析的文章:https://blog.csdn.net/weixin_40318210/article/details/78149692
和一篇关于的@Async和@EnableAsync源码解析的文字:
https://blog.csdn.net/FAw67J7/article/details/80307847

相关准备:

新建工程,pom如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>wz_ling1991</groupId>
	<artifactId>springboot_schedule_demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springboot_schedule_demo</name>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

启动类:

package wz_ling1991.schedule;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class ScheduleApplication {

	public static void main(String[] args) {
		SpringApplication.run(ScheduleApplication.class, args);
	}
}

说明:使用@Async注解时要在启动类上加上@EnableAsync

情况一

只有一个定时任务。
代码片段如下:

    @Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
    public void test1() {
        System.out.println(Thread.currentThread().getName() + ":test1.start" + new Date());
        try {
            Thread.sleep(7000);
        } catch (Exception e) {

        }
        System.out.println(Thread.currentThread().getName() + ":test1.end  " + new Date());
    }

启动运行,结果如下:

cron表达式为每5秒执行一次,任务中sleep7秒,最终输入为每10秒执行一次。
结论:上次任务没有执行完,下次任务不会执行,一直等到该次任务执行完且到任务执行时间点,下一次任务才会执行。

情况二

只有一个定时任务,且任务通过子线程去执行。
代码片段如下:

    @Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
    public void test1() {
        System.out.println("主线程" + Thread.currentThread().getName());
        new Thread(() -> {
            System.out.println("子线程" + Thread.currentThread().getName() + ":test1.start" + new Date());
            try {
                Thread.sleep(7000);
            } catch (Exception e) {

            }
            System.out.println("子线程" + Thread.currentThread().getName() + ":test1.end  " + new Date());
        }).start();
    }

启动运行,结果如下:
结论:通过子线程去执行任务,并不会阻塞主线程的任务调度。(按照cron表达式,依旧是5秒执行一次,而且每次都是新开一个线程。)

情况三

有两个定时任务,任务串行执行。
代码片段如下:

    @Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
    public void test1() {
        System.out.println(Thread.currentThread().getName() + ":test1.start" + new Date());
        try {
            Thread.sleep(7000);
        } catch (Exception e) {

        }
        System.out.println(Thread.currentThread().getName() + ":test1.end  " + new Date());
    }


    @Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
    public void test2() {
        System.out.println(Thread.currentThread().getName() + ":test2.start" + new Date());
        try {
            Thread.sleep(7000);
        } catch (Exception e) {

        }
        System.out.println(Thread.currentThread().getName() + ":test2.end  " + new Date());
    }

启动运行,结果如下:

结论:两个任务共用一个线程,任务串行执行。当到执行时间时,没有获取到线程时,阻塞等待。
ps:请找到ScheduledAnnotationBeanPostProcessor类(org.springframework.scheduling.annotation包下)的finishRegistration方法最后一行this.registrar.afterPropertiesSet();调到该方法中,追踪下去会看到如下代码:

看到这里就明白了任务为什么是串行执行的了。

情况四

有两个定时任务,并行执行。
通过上面,知道了任务串行执行的原因,那么通过修改taskScheduler即可实现。
新增配置类,代码片段如下:

@Configuration
public class ScheduleConfig implements SchedulingConfigurer {
	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
		ScheduledExecutorService taskExecutor = Executors.newScheduledThreadPool(2);
		taskRegistrar.setScheduler(taskExecutor);
	}
}

定时任务代码片段如下:

    @Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
    public void test1() {
        System.out.println(Thread.currentThread().getName() + ":test1.start" + new Date());
        try {
            Thread.sleep(7000);
        } catch (Exception e) {

        }
        System.out.println(Thread.currentThread().getName() + ":test1.end  " + new Date());
    }

    @Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
    public void test2() {
        System.out.println(Thread.currentThread().getName() + ":test2.start" + new Date());
        try {
            Thread.sleep(7000);
        } catch (Exception e) {

        }
        System.out.println(Thread.currentThread().getName() + ":test2.end  " + new Date());
    }

启动运行,结果如下:

结论:两个任务并行执行,由两个不同的线程去调度。ps:这里单个任务还是存在阻塞。
这里给出3个定时任务运行的结果,(poosize之前设置的是2。这里仅为测试说明问题,poosize应根据情况去设置。)

可以看出当任务数超过线程数时,还是会存在阻塞的情况。

情况五

有1个定时任务,并发执行。
注掉刚才的配置类ScheduleConfig,在启动类上增加@EnableAsync,在定时任务上增加@Async
代码片段如下:

    @Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
    @Async
    public void test1() {
        System.out.println(Thread.currentThread().getName() + ":test1.start" + new Date());
        try {
            Thread.sleep(7000);
        } catch (Exception e) {

        }
        System.out.println(Thread.currentThread().getName() + ":test1.end  " + new Date());
    }

启动运行,结果如下:

结论:任务异步执行时,且每次执行任务时,都是新建一个线程。
ps:启动后发现报了一条如下信息。More than one TaskExecutor bean found within the context, and none is named ‘taskExecutor’. Mark one of them as primary or name it ‘taskExecutor’ (possibly as an alias) in order to use it for async processing: [applicationTaskExecutor, taskScheduler]。
查看@EnableAsync

注意我所框出来的内容。有兴趣的可以找到这块的源码对应的看一下。
第一个框的内容大概是要么存在唯一的TaskExecutor,要么存在名字为taskExecutor的bean,否则将采用SimpleAsyncTaskExecutor来执行异步任务。所以线程名打印出来是SimpleAsyncTaskExecutor-i这种类型的。
注意第二个红框,下面的情况(情况六),自定义配置,实现AsyncConfigurer 接口,重写了getAsyncExecutor方法。

情况六

有一个异步任务。
ps:这里注掉了ScheduleConfig。
增加配置类MyAsyncConfigurer,代码片段如下:

@Configuration
public class MyAsyncConfigurer implements AsyncConfigurer {
    @Override
    public Executor getAsyncExecutor(){
        return Executors.newScheduledThreadPool(2);
    }
}

定时任务代码片段如下:

@Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
@Async
public void test1() {
    System.out.println(Thread.currentThread().getName() + ":test1.start" + new Date());
    try {
        Thread.sleep(7000);
    } catch (Exception e) {

    }
    System.out.println(Thread.currentThread().getName() + ":test1.end  " + new Date());
}

启动运行,结果如下:

结论:线程被复用。

情况七

有两个定时任务,一个同步,一个异步。
ps:这里注掉了ScheduleConfig和MyAsyncConfigurer。
任务代码片段如下:

    @Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
    public void test1() {
        System.out.println(Thread.currentThread().getName() + ":test1.start" + new Date());
        try {
            Thread.sleep(7000);
        } catch (Exception e) {

        }
        System.out.println(Thread.currentThread().getName() + ":test1.end  " + new Date());
    }

    @Scheduled(cron = "0/5 * * * * ? ")//每5秒执行一次
    @Async
    public void test2() {
        System.out.println(Thread.currentThread().getName() + ":test2.start" + new Date());
        try {
            Thread.sleep(7000);
        } catch (Exception e) {

        }
        System.out.println(Thread.currentThread().getName() + ":test2.end  " + new Date());
    }

启动运行,结果如下:

结论:同步任务没有收到影响,但是异步任务受到了影响。感觉有什么规律,但是又不好总结,且看结果吧。
ps:这里依然报出了More than one TaskExecutor bean found within the context, and none is named ‘taskExecutor’. Mark one of them as primary or name it ‘taskExecutor’ (possibly as an alias) in order to use it for async processing: [applicationTaskExecutor, taskScheduler]。

放开ScheduleConfig和MyAsyncConfigurer
结果如下:

同步任务依然会存在阻塞的现象,异步任务不存在。符合预期期望。
思考,如果只放开ScheduleConfig,那么执行异步任务时,会是哪个线程去执行呢?结果如下,这块我看的有点迷糊。先把结果描述下,线程名分别为pool-1-thread-i和task-i。
线程池size为2。

线程池size为4:实际上多跑一段时间每个线程都有执行同步任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值