spring任务执行器与任务调度器(TaskExecutor And TaskScheduler)

对于多线程及周期性调度相关的操作,spring框架提供了TaskExecutor和TaskScheduler接口为异步执行和任务调度。并提供了相关实现类给开发者使用。(只记录采用注解的使用形式,对于XML的使用形式不做笔记。)
Spring官方对TaskExecutor的相关解释:

Spring的TaskExecutor接口与java.util.concurrent.Executor接口相同。该接口具有单个方法(execute(Runnable task)),该方法根据线程池的语义和配置接受要执行的任务。

二话(写)不说,自说(写)自话(字)直接拐上了

TaskExecutor接口相关实现类
实现类名对应解释(直接甩翻译了)
SyncTaskExecutor该实现类不会执行异步调用。 相反,每次调用都在调用的线程中进行(翻译过来也即同步任务执行器)。 它主要用于不需要多线程的情况,例如在简单的测试用例中。
SimpleAsyncTaskExecutor此实现不会重用任何线程。 相反,它为每次调用启动一个新线程。 但是,它确实支持并发限制,该限制会阻止超出限制的任何调用,直到释放插槽为止。(说简单了,就是要使用了直接创建一个线程)
ConcurrentTaskExecutor此实现是java.util.concurrent.Executor实例的适配器。很少需要直接使用ConcurrentTaskExecutor**(官网自己都觉得很少使用,不过相对于ThreadPoolTaskExecutor,官网推荐如果ThreadPoolTaskExecutor不够灵活,无法满足需求,则可以使用ConcurrentTaskExecutor)**。
ThreadPoolTaskExecutor杀手锏级的任务调度器(最常用),可以说已经足够满足我们的需求了(除非,非常非常特例才使用ConcurrentTaskExecutor)。官网翻译重要片段:公开了bean属性,用于配置java.util.concurrent.ThreadPoolExecutor并将其包装在TaskExecutor中
WorkManagerTaskExecutor此实现使用CommonJ WorkManager作为其后备服务提供程序,并且是在Spring应用程序上下文中在WebLogic或WebSphere上设置基于CommonJ的线程池集成的中心便利类。
DefaultManagedTaskExecutor此实现在JSR-236兼容的运行时环境(例如Java EE 7+应用程序服务器)中使用JNDI获取的ManagedExecutorService,为此目的替换CommonJ WorkManager。(说明了就是依赖环境)

其中可能今后工作中会用到的(包括测试) SyncTaskExecutor、SimpleAsyncTaskExecutor、ConcurrentTaskExecutor、ThreadPoolTaskExecutor此四个实现类。重点关注ThreadPoolTaskExecutor此类。
:以上均实现了spring提供的
TaskExecutor**接口。

Spring官方对TaskSheduler接口的相关解释:

用于在将来的某个时间点调度任务。

TaskScheduler接口相关实现类
实现类名对应解释
ConcurrentTaskScheduler该类实际继承了ConcurrentTaskExecutor对象。只是实现了TaskScheduler接口,增加了相关定时调度任务的方法。
ThreadPoolTaskSchedulerspring对该类设计原则同ThreadPoolTaskExecutor类。是为了定时调度任务不依赖相关的运行容器(例如weblogic、WebSphere等)。其底层委托给ScheduledExecutorService,向外暴露相关的常见bean配置属性。

接下来对以上相应的实现类通过注解的形式进行相应的测试
首先进行解释要用到的几个注解,对这几个注解总结如下表

注解名解释
@EnableAsync开启异步执行。官方文档中解释:该注解添加到@Configuration标注的类上以开始异步执行。开启后@Async标注的方法或类即可异步执行。
@EnableScheduling开启定时调度。官方文档解释也是配合@Configuration一起使用。开启后@Scheduled注解标注的方法即可自动定时(或延迟)执行。
@Async异步执行注解。可标注类和方法。标注类时,则该类下所有方法均可使用异步执行。标注方法时,则该方法可使用异步执行。当标注有@Configuration注解的配置类上标注了@EnableAsync注解后即可生效。
@Scheduled标注相关方法后,如果配置类标注了@EnableScheduling后即可开启定时调度任务。
  • 接下来先测试异步执行器(TaskExecutor)

使用方式1(直接使用spring容器中的ThreadPoolTaskExecutor):

先通过@Bean注解将ThreadPoolTaskExecutor放入Spring容器中:

@Configuration
public class ExecutorBean {
	/**
	 * 执行器ThreadPoolTaskExecutor
	 */
	@Bean
	public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(10); // 设置核心线程池大小(这里设置为初始化10个)
		executor.setMaxPoolSize(30); // 设置最大线程池大小(当核心线程池不够用时候,会自动在原基础上增加。最大为30个)
		executor.setQueueCapacity(2000); // 设置队列容量为2000个。
		return executor;
	}
}

然后在标注有@Configuration注解的配置类或SpringBoot启动类上添加@EntityAsync注解。这里在标有@SpringBootApplication注解的SpringBoot启动类的入口方法上添加@EntityAsync注解。

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

然后在需要采取异步执行的方法上(或类上,此测试使用方法)标注@Async注解:

@Async(value="threadPoolTaskExecutor")
public void testThreadPoolTaskExecutor() {
    System.err.println("---  testThreadPoolTaskExecutor  --");
}

测试用例:

@Test
public void testThreadPoolTaskExecutor() {
    System.err.println("--- 开始 ---");
    taskExecutorService.testThreadPoolTaskExecutor();
    System.err.println("--- 结束 ---");
}

执行结果:

--- 开始 ---
--- 结束 ---
---  testThreadPoolTaskExecutor  --

使用方式2(通过修改默认的线程池配置,即实现AsyncConfigurer接口,并重写其中的getAsyncExecutor方法[因为是JDK8提供的default方法,所以才称为重写]):
首先,先实现AsyncConfigurer接口,重写getAsyncExecutor方法并将此实现类作为配置类装载进spring容器中(记:对于void返回类型,异常未被捕获且无法传输,所以getAsyncUncaughtExceptionHandler方法用于处理异步调用后出现异常的情况。这里仅仅记录未出现异常的测试),同时添加@EnableAsync开启可异步调用(也可以在springBoot启动类中的入口方法上添加)。

@Configuration
@EnableAsync
public class CustomAsyncConfigurer implements AsyncConfigurer {
	/**
	 * 设置线程池相关的配置
	 * @return ThreadPoolTaskExecutor
	 */
	@Override
	public Executor getAsyncExecutor() {
		ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
		executor.setCorePoolSize(10); // 设置核心线程池大小(这里设置为初始化10个)
		executor.setMaxPoolSize(30); // 设置最大线程池大小(当核心线程池不够用时候,会自动在原基础上增加。最大为30个)
		executor.setQueueCapacity(2000); // 设置队列容量为2000个。
        executor.initialize();
		return executor;
	}
}

然后可以直接使用@Async注解标注需要异步执行的方法(或类上,此测试使用方法)

@Async
public void testThreadPoolTaskExecutor(Long id) {
    System.err.println("---  testThreadPoolTaskExecutor  --" + id);
}

测试用例:

@Test
public void testThreadPoolTaskExecutor() {
    System.err.println("--- 开始前 ---");
    System.err.println("--- 开始 ---");
    for(int i =0;i<20;i++) {
        taskExecutorService.testThreadPoolTaskExecutor(new Long(i));
    }
    System.err.println("--- 结束 ---");
    System.err.println("--- 结束后 ---");
}

测试结果:

--- 开始前 ---
--- 开始 ---
---  testThreadPoolTaskExecutor  --0
---  testThreadPoolTaskExecutor  --1
---  testThreadPoolTaskExecutor  --2
---  testThreadPoolTaskExecutor  --3
---  testThreadPoolTaskExecutor  --4
---  testThreadPoolTaskExecutor  --5
---  testThreadPoolTaskExecutor  --6
---  testThreadPoolTaskExecutor  --7
---  testThreadPoolTaskExecutor  --8
---  testThreadPoolTaskExecutor  --9
---  testThreadPoolTaskExecutor  --10
---  testThreadPoolTaskExecutor  --11
---  testThreadPoolTaskExecutor  --12
---  testThreadPoolTaskExecutor  --13
---  testThreadPoolTaskExecutor  --14
---  testThreadPoolTaskExecutor  --17
---  testThreadPoolTaskExecutor  --16
---  testThreadPoolTaskExecutor  --15
--- 结束 ---
--- 结束后 ---
---  testThreadPoolTaskExecutor  --19
---  testThreadPoolTaskExecutor  --18

**使用方式3:因为ThreadPoolTaskScheduler实现了TaskExecutor相关的接口。所以同样可以用ThreadPoolTaskScheduler替换ThreadPoolTaskExecutor来调用异步执行器。
同样实现AsyncConfigurer接口,重写getAsyncExecutor方法并将此实现类作为配置类装载进spring容器中。只是此时用ThreadPoolTaskScheduler替换ThreadPoolTaskExecutor类作为任务执行器

@Configuration
@EnableAsync
public class CustomAsyncConfigurer implements AsyncConfigurer {
    /**
     * 设置线程池相关的配置
     * @return ThreadPoolTaskExecutor
     */
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
        executor.setPoolSize(30);
        executor.initialize();
        return executor;
    }
}

然后用@Asnyc标注方法

@Async
public void testThreadPoolTaskScheduler(Long id) {
    System.err.println("---  testThreadPoolTaskExecutor  --" + id);
}

测试

@Test
public void testThreadPoolTaskScheduler() {
    System.err.println("--- 开始前 ---");
    System.err.println("--- 开始 ---");
    for(int i =0;i<20;i++) {
        taskExecutorService.testThreadPoolTaskScheduler(new Long(i));
    }
    System.err.println("--- 结束 ---");
    System.err.println("--- 结束后 ---");
}

经过测试,用例3没啥效果...测试方式可能有误。同样的异步调用ThreadPoolTaskScheduler类可能不能当做ThreadPoolTaskExecutor 类使用。虽然同样实现了TaskExecutor接口(看来得看底层源码了,现在仅仅记录下来先)

  • 现在来测试定时调度器(TaskScheduler)

使用方式1(直接使用@EnableScheduling开启定时调度任务,然后对需要定时调度的方法用@Sheduled注解标注):
直接在SpringBoot启动器类中的入口方法上标注或标注了@Configuration注解的配置类上使用@EnableScheduling注解

//在启动类上添加
@SpringBootApplication
@EnableScheduling
public class ScheduleApplication {
	public static void main(String[] args) {
		SpringApplication.run(ScheduleApplication.class, args);
	}
}

//或在在配置类上标注
@Configuration
@EnableScheduling
public class CustomScheduleConfigurer implements AsyncConfigurer {
//...
}

需要在定时任务的方法上添加@Scheduled

@Scheduled(fixedRate = 300)  //表示每300毫秒调用一次该方法
@Override
public void threadPoolTaskScheduler() {
    System.err.println("---  threadPoolTaskScheduler  --");
}

测试用例

@Test
public void schedulers() throws Exception {
    Thread.sleep(50000000);   //让线程睡上一段时间,以便查看效果
}

因为应用一启动后定时调度器便会开始执行。如果测试用例不使用线程睡眠的话程序会一瞬间执行结束,有可能看不到效果。
:@Scheduled注解支持Quartz的CRON表达式来规划定时任务(仅做记录:SPRING官网示例)。
spring框架支持Quartz来使用定时调度任务

这里简单记录其各个表达式含义:

@Scheduled注解的各个参数与其含义
参数名类型含义
cronString使用表达式的方式定义任务执行时间
zoneString可以通过它设定区域时间(时区)
fixedDelaylong表示从上一个任务完成后到下一个任务开始时的时间间隔,单位毫秒
fixedDelayStringString同fixedDelay,只是为字符串值,可使用SPEL表达式来引入配置文件配置
inittalDelaylong初始化延时时间。spring容器初始化后首次任务延时多久开始执行,单位毫秒
inittalDelayStringString同 inittalDelay,值为字符串值。可使用SPEL表达式来引入配置文件配置
fixedRatelong每次执行任务的间隔时间,单位毫秒
fixedRateStringString每次执行任务的间隔时间,单位为字符串值。可使用SPEL表达式来引入配置文件配置

CRON表达式有6~7个空格分隔得时间元素,按顺序依次为:
秒 分 时 天 月 星期 年 其中年可以是不用配置的元素。
例:
0 0 0 ? * WED
表示每个星期三0点整执行任务。其中因为天和星期会产生定义上的冲突。所以采用了通配符。以下为通配符含义

通配符符号含义
*表示任意
?表示不指定,用于处理天和星期的冲突
-指定时间区间
/指定时间间隔
L指最后
#第几
,列举多个项

以下为几个难点的例子:

示例描述
0 0/6 19,22 * * ?每天19点至19点59和22点至22点59分俩时间段内每6分钟执行一次
0 10-16 22 * * ?每天的22点至22点16分每分钟执行一次
0 0 19 ? * MON-FRI周一到周五的每天19点执行一次
0 23 23 ? * 5L 2019-20222019年至2022年的每月最后一个周四的23点23分执行
0 16 22 ? * 6#2每月第二周周五的22点16分执行一次

使用方式2(实现SchedulingConfigurer配置类,规定定时任务)
首先实现SchedulingConfigurer配置类,并启用定时调度任务

@Configuration
@EnableScheduling
public class CustomSchedulerConfigurer implements SchedulingConfigurer {
	@Override
	public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
		IntervalTask task = new IntervalTask(new Runnable() {
			@Override
			public void run() {
				System.err.println("----间隔执行----");	
			}
		}, 2000);
		taskRegistrar.addFixedDelayTask(task);    //taskRegistrar有更多相关方法来执行定时调度任务。测试用例先做简单记录
	}
}

然后进行测试

@Test
public void schedulers() throws Exception {
	Thread.sleep(50000000);    //同样让线程睡上一段时间,以便查看效果
}

测试中并未处理异步执行后如果出现异常的情况。异常情况发生后如何处理之后再做记录。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值