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

在此之前推荐一篇关于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:实际上多跑一段时间每个线程都有执行同步任务。

猜你喜欢

转载自blog.csdn.net/wz_ling1991/article/details/83930409