006Spring技巧001定时器

1 简介

出了使用Quartz框架实现定时功能外,Spring自身也集成了一个定时器,使用Scheduled注解即可实现定时器的功能。

Scheduled注解可以作为一个触发源添加到一个方法中,经常用于定时任务。

2 使用定时器

2.1 配置开启定时任务功能

2.1.1 基于XML

需要设置task名称空间的annotation-driven标签:

1 <!-- 配置扫描器。 -->
2 <context:component-scan base-package="com.demo.task" />
3 <!-- 开启基于注解的配置。 -->
4 <task:annotation-driven />

2.1.2 基于注解

需要在被Configuration注解修饰的配置类上添加EnableScheduling注解:

1 @Configuration
2 @EnableScheduling
3 @ComponentScan(basePackages="com.demo.task")
4 public class DemoConfiguration {
5 
6     public DemoConfiguration() {
7         System.out.println("DemoConfiguration DemoConfiguration() ...");
8     }
9 }

2.2 配置定时任务

2.2.1 基于XML

使用这种方式的前提是在XML配置文件中开启了定时任务功能,如果是在注解中开启的定时任务,可以使用后面的另一种方式。

创建定时任务类:

 1 @Component
 2 public class DemoTask {
 3 
 4     public void taskA() {
 5         DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 6         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********A任务每10秒执行一次进入测试");
 7         try {
 8             Thread.sleep(20000);
 9         } catch (InterruptedException e) {
10             e.printStackTrace();
11         }
12         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********A任务执行完毕");
13     }
14 
15     public void taskB() {
16         DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
17         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********B任务每5秒执行一次进入测试");
18         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********B任务执行完毕");
19     }
20 }

在XML文件中将创建定时任务,引用该类的组件和类中的任务方法:

1 <!-- 配置定时任务。 -->
2 <task:scheduled-tasks>
3     <task:scheduled ref="demoTask" method="taskA" cron="0/10 * * * * ?" />
4     <task:scheduled ref="demoTask" method="taskB" cron="0/5 * * * * ?" />
5 </task:scheduled-tasks>

2.2.2 基于注解

如果是在注解中开启的定时任务功能,可以使用这种方式,当然了,如果是在XML中开启的定时任务,也可以使用这种方式。

创建定时任务类,使用Scheduled注解修饰任务方法,设置任务执行的规则:

 1 @Component
 2 public class DemoTask {
 3 
 4     @Scheduled(cron = "0/10 * * * * ?")
 5     public void taskA() {
 6         DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 7         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********A任务每10秒执行一次进入测试");
 8         try {
 9             Thread.sleep(20000);
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12         }
13         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********A任务执行完毕");
14     }
15 
16     @Scheduled(cron = "0/5 * * * * ?")
17     public void taskB() {
18         DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
19         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********B任务每5秒执行一次进入测试");
20         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********B任务执行完毕");
21     }
22 }

2.3 测试类

2.3.1 获取基于XML的容器

如果是基于XML的容器,可以使用如下代码进行测试:

1 public class DemoTest {
2 
3     public static void main(String[] args) throws Exception {
4         ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
5         Thread.sleep(3000000);
6         context.close();
7     }
8 }

2.3.2 获取基于注解的容器

如果是基于注解的容器,可以使用如下代码进行测试:

1 public class DemoTest {
2 
3     public static void main(String[] args) throws Exception {
4         AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DemoConfiguration.class);
5         Thread.sleep(3000000);
6         context.close();
7     }
8 }

2.4 配置调度线程池

该配置用于对不同的定时任务进行调度,当有多个定时任务同时被触发的时候,该配置才会起作用。

多个定时任务指的是,有多个方法都有自己独立的任务执行计划,每个方法都是一个任务。同一个方法被执行多次并不是多任务,只是一个任务被多次执行。

如果没有配置该功能,两个定时任务则会采用单线程方式调用,只有两个定时任务执行完成后,下一个定时任务才能执行。如果配置了该功能,即便有定时任务还在执行,另一个定时任务也能在被触发时顺利执行。

但是,当一个任务还在执行中,此时此任务又被触发,如果想要控制这种情况下的线程问题,需要配置执行线程池。

2.4.1 基于XML

在XML中配置了定时任务后,还可以在XML配置文件中使用task名称空间中的scheduler标签配置调度任务可用的线程池。

修改配置文件并添加:

1 <!-- 开启基于注解的配置。 -->
2 <task:annotation-driven scheduler="demoScheduler" />
3 <!-- 配置调度线程池。 -->
4 <task:scheduler id="demoScheduler" pool-size="5" />

常用属性如下:

1 id:唯一标识线程池。如果有多个线程池,需要在annotation-driven标签中使用scheduler属性指定线程池,如果只有一个可以不指定。
2 keep-alive:指定维护线程所需的空闲时间,单位是秒,默认是60。
3 pool-size:指定线程池的初始大小、最大大小。如果只有一个数值,则表示固定大小。
4 queue-capacity:指定等待队列的容量。
5 rejection-policy:指定当等待队列满时的策略,分为丢弃、由任务执行器直接运行等方式。

其中,rejection-policy属性的取值如下:

1 ABORT(缺省):抛出TaskRejectedException异常,然后不执行。
2 CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行(不再异步)。
3 DISCARD:不执行,也不抛出异常即放弃该线程。
4 DISCARD_OLDEST:丢弃队列中最旧的那个任务。

2.4.2 基于注解

在配置类中配置了定时任务后,还可以在配置类中配置调度任务可用的线程池。

修改配置类并添加:

1 @Bean
2 public ThreadPoolTaskScheduler scheduler() {
3     ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
4     scheduler.setPoolSize(10);
5     scheduler.setThreadNamePrefix("demoScheduler-");
6     scheduler.setAwaitTerminationSeconds(600);
7     scheduler.setWaitForTasksToCompleteOnShutdown(true);
8     return scheduler;
9 }

常用属性如下:

1 CorePoolSize:核心线程数量,线程池创建时候初始化的线程数。
2 MaxPoolSize:最大线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程。
3 QueueCapacity:缓冲队列,用来缓冲执行任务的队列。
4 KeepAliveSeconds:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁。
5 ThreadNamePrefix:线程池名称,用于打印信息,后面的数字是线程编号。
6 WaitForTasksToCompleteOnShutdown:线程池关闭时,是否等待所有任务都完成再继续销毁其他的Bean。
7 AwaitTerminationSeconds:设置线程池中任务的等待时间,如果超过就强制销毁。
8 RejectedExecutionHandler:缓冲池溢出的处理策略。

其中,RejectedExecutionHandler属性的取值如下:

1 AbortPolicy(缺省):抛出TaskRejectedException异常,然后不执行。
2 CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行(不再异步)。
3 DiscardPolicy:不执行,也不抛出异常即放弃该线程。
4 DiscardOldestPolicy:丢弃队列中最旧的那个任务。

2.5 配置执行线程池

该配置用于对定时任务进行调度,当有多个定时任务同时被触发的时候,该配置才会起作用。

2.5.1 基于XML

在XML中配置了定时任务后,还可以在XML配置文件中使用task名称空间中的executor标签配置调度任务可用的线程池。

在定时任务类中的任务方法上添加Async注解:

 1 @Component
 2 public class DemoTask {
 3 
 4     @Scheduled(cron = "0/10 * * * * ?")
 5     @Async
 6     public void taskA() {
 7         DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 8         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********A任务每10秒执行一次进入测试");
 9         try {
10             Thread.sleep(20000);
11         } catch (InterruptedException e) {
12             e.printStackTrace();
13         }
14         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********A任务执行完毕");
15     }
16 
17     @Scheduled(cron = "0/5 * * * * ?")
18     @Async
19     public void taskB() {
20         DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
21         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********B任务每5秒执行一次进入测试");
22         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********B任务执行完毕");
23     }
24 }

修改配置文件并添加:

1 <!-- 开启基于注解的配置。 -->
2 <task:annotation-driven executor="demoExecutor" />
3 <!-- 配置执行线程池。 -->
4 <task:executor id="demoExecutor" pool-size="5" />

常用属性如下:

1 id:唯一标识线程池。如果有多个线程池,需要在annotation-driven标签中使用executor属性指定线程池。
2 keep-alive:指定维护线程所需的空闲时间,单位是秒,默认是60。
3 pool-size:指定线程池的初始大小、最大大小。如果只有一个数值,则表示固定大小。
4 queue-capacity:指定等待队列的容量。
5 rejection-policy:指定当等待队列满时的策略,分为丢弃、由任务执行器直接运行等方式。

其中,rejection-policy属性的取值如下:

1 ABORT(缺省):抛出TaskRejectedException异常,然后不执行。
2 CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行(不再异步)。
3 DISCARD:不执行,也不抛出异常即放弃该线程。
4 DISCARD_OLDEST:丢弃队列中最旧的那个任务。

2.5.2 基于注解

在配置类中配置了定时任务后,还可以在配置类中配置调度任务可用的线程池。

在定时任务类上添加EnableAsync注解,然后在任务方法上添加Async注解并指明使用的executor名称:

 1 @Component
 2 @EnableAsync
 3 public class DemoTask {
 4 
 5     @Scheduled(cron = "0/10 * * * * ?")
 6     @Async("executor")
 7     public void taskA() {
 8         DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
 9         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********A任务每10秒执行一次进入测试");
10         try {
11             Thread.sleep(20000);
12         } catch (InterruptedException e) {
13             e.printStackTrace();
14         }
15         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********A任务执行完毕");
16     }
17 
18     @Scheduled(cron = "0/5 * * * * ?")
19     @Async("executor")
20     public void taskB() {
21         DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
22         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********B任务每5秒执行一次进入测试");
23         System.out.println(Thread.currentThread().getName() + " >>> " + sdf.format(new Date()) + "*********B任务执行完毕");
24     }
25 }

修改配置类并添加:

1 @Bean
2 public AsyncTaskExecutor executor() {
3     ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
4     executor.setCorePoolSize(7);
5     executor.setMaxPoolSize(42);
6     executor.setQueueCapacity(11);
7     executor.setThreadNamePrefix("demoExecutor-");
8     return executor;
9 }

常用属性如下:

1 CorePoolSize:核心线程数量,线程池创建时候初始化的线程数。
2 MaxPoolSize:最大线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程。
3 QueueCapacity:缓冲队列,用来缓冲执行任务的队列。
4 KeepAliveSeconds:当超过了核心线程出之外的线程在空闲时间到达之后会被销毁。
5 ThreadNamePrefix:线程池名称,用于打印信息,后面的数字是线程编号。
6 WaitForTasksToCompleteOnShutdown:线程池关闭时,是否等待所有任务都完成再继续销毁其他的Bean。
7 AwaitTerminationSeconds:设置线程池中任务的等待时间,如果超过就强制销毁。
8 RejectedExecutionHandler:缓冲池溢出的处理策略。

其中,RejectedExecutionHandler属性的取值如下:

1 AbortPolicy(缺省):抛出TaskRejectedException异常,然后不执行。
2 CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行(不再异步)。
3 DiscardPolicy:不执行,也不抛出异常即放弃该线程。
4 DiscardOldestPolicy:丢弃队列中最旧的那个任务。

 

posted @ 2022-01-25 00:16  执笔未来  阅读(35)  评论(0编辑  收藏  举报