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:丢弃队列中最旧的那个任务。