02、异步编程
“二哥,据说 Spring 可以通过 @Async 来实现异步编程,你能给我详细说说吗?”
“没问题啊。”
新建一个 AsyncService 类,内容如下:
@Async 注解用在 public 方法上,表明 execute() 方法是一个异步方法。
新建一个 AsyncConfig 类,内容如下:
@Configuration @EnableAsync public class AsyncConfig { @Bean public AsyncService getAsyncService() { return new AsyncService(); } }
在配置类上使用 @EnableAsync 注解用以开启异步编程,否则 @Async 注解不会起作用。
新建一个 AsyncMain 类,内容如下:
public class AsyncMain { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AsyncConfig.class); AsyncService service = context.getBean(AsyncService.class); for (int i = 0; i < 10; i++) { service.execute(); } }
程序输出结果如下:
SimpleAsyncTaskExecutor-1
SimpleAsyncTaskExecutor-9
SimpleAsyncTaskExecutor-7
SimpleAsyncTaskExecutor-8
SimpleAsyncTaskExecutor-10
SimpleAsyncTaskExecutor-3
SimpleAsyncTaskExecutor-2
SimpleAsyncTaskExecutor-4
SimpleAsyncTaskExecutor-6
SimpleAsyncTaskExecutor-5
OK,结果符合我们的预期,异步编程实现了。就像你看到的那样,Spring 提供了一个默认的 SimpleAsyncTaskExecutor 用来执行线程,我们也可以在方法级别和应用级别上对执行器进行配置。
1)方法级别
新建 AsyncConfig 类,内容如下:
@Configuration
@EnableAsync public class AsyncConfig { @Bean public AsyncService getAsyncService() { return new AsyncService(); } @Bean(name = "threadPoolTaskExecutor") public Executor threadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); return executor; } }
在配置类中创建了一个返回类型为 Executor 的 Bean,其名称定义为“threadPoolTaskExecutor”,并且重新设置了 ThreadPoolTaskExecutor 的核心线程池大小,默认为 1,现在修改为 5。
新进 AsyncService 类,内容如下: public class AsyncService { @Async("threadPoolTaskExecutor") public void execute() { System.out.println(Thread.currentThread().getName()); } }
@Async 注解上需要指定我们之前配置的线程池执行器“threadPoolTaskExecutor”。
新建 AsyncMain 类,内容如下:
public class AsyncMain { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AsyncConfig.class); AsyncService service = context.getBean(AsyncService.class); for (int i = 0; i < 10; i++) { service.execute(); } } }
程序运行结果如下:
threadPoolTaskExecutor-1
threadPoolTaskExecutor-2
threadPoolTaskExecutor-4
threadPoolTaskExecutor-3
threadPoolTaskExecutor-5
threadPoolTaskExecutor-3
threadPoolTaskExecutor-2
threadPoolTaskExecutor-4
threadPoolTaskExecutor-1
threadPoolTaskExecutor-5
从结果中可以看得出,线程池执行器变成了“threadPoolTaskExecutor”,并且大小为 5。
2)应用级别
新建 AsyncConfig 类,内容如下:
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Bean public AsyncService getAsyncService() { return new AsyncService(); } @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(3); executor.initialize(); return executor; } }
需要实现 AsyncConfigurer 接口,并重写 getAsyncExecutor() 方法,这次设置线程池的大小为 3。注意执行器要执行一次 initialize() 方法。
新进 AsyncService 类,内容如下:
public class AsyncService {
@Async
public void execute() {
System.out.println(Thread.currentThread().getName());
}
}
新建 AsyncMain 类,内容如下:
public class AsyncMain { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AsyncConfig.class); AsyncService service = context.getBean(AsyncService.class); for (int i = 0; i < 10; i++) { service.execute(); } } }
程序运行结果如下:
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-2
ThreadPoolTaskExecutor-1
ThreadPoolTaskExecutor-3
从结果中可以看得出,线程池执行器变成了“ThreadPoolTaskExecutor”,并且大小为 3。
03、计划任务
“二哥,据说 Spring 可以通过 @Scheduled 来实现计划任务,你能给我详细说说怎么实现吗?”
“没问题啊。”
新建一个 ScheduledService 类,内容如下:
@Service public class ScheduledService { @Scheduled(fixedDelay = 1000) public void scheduleFixedDelayTask() { System.out.println( "固定时间段后执行任务 - " + System.currentTimeMillis() / 1000); } @Scheduled(fixedRate = 1000) public void scheduleFixedRateTask() { System.out.println( "固定的频率执行任务 - " + System.currentTimeMillis() / 1000); } @Scheduled(cron = "0/2 * * * * ?") public void scheduleTaskUsingCronExpression() { long now = System.currentTimeMillis() / 1000; System.out.println( "Cron 表达式执行任务 - " + now); } }
@Service 注解用于指定 ScheduledService 类为一个业务层的 Bean。@Scheduled 注解用于指定当前方法(返回类型为 void,无参)为一个任务执行方法,常见的用法有以下 3 种:
1)fixedDelay 用于确保任务执行的完成时间与任务下一次执行的开始时间之间存在 n 毫秒的延迟,下一次任务执行前,上一次任务必须执行完。
2)fixedRate 用于确保每 n 毫秒执行一次计划任务,即使最后一次调用可能仍在运行。
3)Cron 表达式比 fixedDelay 和 fixedRate 都要灵活,由 7 个部分组成,各部分之间用空格隔开,其完整的格式如下所示:
Seconds Minutes Hours Day-of-Month Month Day-of-Week Year
1
单词都很简单,就不用我翻译了。其中 Year 是可选项。常见的范例如下所示:
*/5 * * * * ? 每隔 5 秒执行一次
0 */1 * * * ? 每隔 1 分钟执行一次
0 0 23 * * ? 每天 23 点执行一次
0 0 1 * * ? 每天凌晨 1 点执行一次:
0 0 1 1 * ? 每月 1 号凌晨 1 点执行一次
0 0 23 L * ? 每月最后一天 23 点执行一次
0 0 1 ? * L 每周星期天凌晨 1 点执行一次
0 26,29,33 * * * ? 在 26 分、29 分、33 分执行一次
0 0 0,13,18,21 * * ? 每天的 0 点、13 点、18 点、21 点各执行一次
新建 ScheduledConfig 类,内容如下:
@Configuration @EnableScheduling @ComponentScan("high.scheduled") public class ScheduledConfig { }
@EnableScheduling 注解用于开启计划任务。@ComponentScan 注解用于扫描当前包下的类,如果它使用了注解(比如 @Service),就将其注册成为一个 Bean。
新建 ScheduledMain 类,内容如下:
public class ScheduledMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScheduledConfig.class);
}
}
程序运行结果如下:
固定的频率执行任务 - 1584666273
固定时间段后执行任务 - 1584666273
Cron 表达式执行任务 - 1584666274
固定的频率执行任务 - 1584666274
固定时间段后执行任务 - 1584666274
固定的频率执行任务 - 1584666275
固定时间段后执行任务 - 1584666275
Cron 表达式执行任务 - 1584666276
从结果中可以看得出,如果任务之间没有冲突的话,fixedDelay 任务之间的间隔和 fixedRate 任务之间的间隔是相同的,都是 1 秒;Cron 表达式任务与上一次任务之间的间隔为 2 秒。
“二哥,这篇文章中的示例代码你上传到 GitHub 了吗?”
“你到挺贴心啊,三妹。传送门~”
“二哥,你教得真不错,我完全学会了,一点也不枯燥。”
“那必须得啊,期待下一篇吧?”
“那是当然啊,期待,非常期待,望眼欲穿的感觉。”
请允许我热情地吐槽一下,这篇文章我不希望再被喷了,看在我这么辛苦搞原创(创意+干货+有趣)的份上,多鼓励鼓励好不好?别瞅了,点赞呗,你最美你最帅。