一、概述
1、jdk的线程池和任务调用器分别由ExecutorService、ScheduledExecutorService定义,继承关系如下:
ThreadPoolExecutor:ExecutorService的实现类,其构造函数提供了灵活的参数配置,可构造多种类型的线程池,详细可参考JAVA进阶----ThreadPoolExecutor机制
ScheduledThreadPoolExecutor:ScheduledExecutorService的实现类,用于任务调度
2、spring task对定时任务的两个抽象:
- TaskExecutor:与jdk中Executor相同,引入的目的是为定时任务的执行提供线程池的支持
- TaskScheduler:对定时任务的抽象
继承关系如下:
任务执行器与调度器的实现类分别为ThreadPoolTaskExecutor、ThreadPoolTaskScheduler
TaskScheduler需要传入一个Runnable的任务做为参数,并指定需要周期执行的时间或者触发器(Trigger
)。
spring定义了Trigger接口的实现类CronTrigger,支持使用cron表达式指定定时策略,使用如下:
scheduler.schedule(task, new CronTrigger("30 * * * * ?"));
二、定时任务
spring定时任务的使用和配置非常简单,支持xml配置和注解两个方式
1、XML配置方式
任务类
@Component public class TestTask { public void job() { System.out.println("hello matt!"); } }
配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd"> <context:component-scan base-package="cn.matt.schedule"/> <!-- 任务调度器--> <task:scheduler id="myScheduler" pool-size="10"/> <!-- 任务配置--> <task:scheduled-tasks scheduler="myScheduler"> <task:scheduled ref="testTask" method="job" cron="0/2 * * * * ?"/> </task:scheduled-tasks> </beans>
测试
public class TaskTest { @Test public void test() throws InterruptedException { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-context.xml"); TimeUnit.SECONDS.sleep(10); } }
2、注解方式
任务类
@Component public class TestTask { @Scheduled(cron = "0/2 * * * * ?") public void job() { System.out.println("hello matt!"); } }
配置
<!-- 任务调度器--> <task:scheduler id="myScheduler" pool-size="10"/> <!-- 开启任务注解--> <task:annotation-driven scheduler="myScheduler"/>
测试代码与xml配置方式相同
补充说明:
- 使用<task:executor>配置任务执行器,即实例化ThreadPoolTaskExecutor
- 使用<task:scheduler>配置任务调度器,即实例化ThreadPoolTaskScheduler
- 两种方式的任务调度器不指定时,默认会使用只有一个线程的调用器,关于配置的详细介绍和默认参数,可参考xsd文档 http://www.springframework.org/schema/task/spring-task-4.1.xsd
疑问:
有些项目同时指定任务执行器和调度器,如下:
<!-- 启用注解驱动的定时任务 --> <task:annotation-driven scheduler="myScheduler" executor="myExecutor"/> <!-- 配置定时任务的线程池 --> <task:scheduler id="myScheduler" pool-size="10"/> <task:executor id="myExecutor" pool-size="10" />
存在两个线程池,两者的关系是怎样的?执行器用的是哪个线程池?
经验证,定时任务执行时,使用的是任务调度器的线程池,任务执行器的设置对定时任务的执行没有影响,测试代码如下:
任务类
@Component public class TestTask { @Scheduled(cron = "0/5 * * * * ?") public void job1() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(String.format("%s*******%s", sdf.format(new Date()), "job1")); } @Scheduled(cron = "0/10 * * * * ?") public void job2() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(String.format("%s*******%s", sdf.format(new Date()), "job2")); try { TimeUnit.SECONDS.sleep(15); } catch (InterruptedException e) {} } }
配置与疑问中相同
测试(测试时,通过改变线程池的大小进行验证)
public class TaskTest { @Test public void test() throws InterruptedException { ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-context.xml"); TimeUnit.SECONDS.sleep(1000); } }
3、cron表达式
spring支持6个参数的cron表达式,格式如下:
{秒} {分} {时} {日期} {月} {星期}
- 秒:必填项,允许的值范围是0-59,支持的特殊符号包括'-*/,',','表示特定的某一秒才会触发任务,'-'表示一段时间内会触发任务,'*'表示每一秒都会触发,'/'表示从哪一个时刻开始,每隔多长时间触发一次任务。
- 分:必填项,允许的值范围是0-59,支持的特殊符号和秒一样,含义类推
- 时:必填项,允许的值范围是0-23,支持的特殊符号和秒一样,含义类推
- 日期:必填项,允许的值范围是1-31,支持的特殊符号相比秒多了
?
,表示与{星期}互斥,即意味着若明确指定{星期}触发,则表示{日期}无意义,以免引起冲突和混乱 - 月:必填项,允许的值范围是1-12(JAN-DEC),支持的特殊符号与秒一样,含义类推
- 星期:必填项,允许值范围是1~7 (SUN-SAT),1代表星期天(一星期的第一天),以此类推,7代表星期六,支持的符号相比秒多了
?
,表达的含义是与{日期}互斥,即意味着若明确指定{日期}触发,则表示{星期}无意义。
示例:
0 0 12 * * ? 每天中午12点触发
0 * 14 * * ? 每天下午2点到下午2:59期间的每1分钟触发
0 0/5 14 * * ? 每天下午2点到下午2:55期间的每5分钟触发
0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
关于cron表达式的详细介绍可参考spring定时任务详解
三、异步调用
spring提供@Async注解,可很方便的实现异步调用,简单示例如下:
接口及实现类
public interface Hello { void doSomething1(); void doSomething2(); }
@Component public class HelloImpl implements Hello { @Async @Override public void doSomething1() { System.out.println(String.format("thread:%d **** doSomething1", Thread.currentThread().getId())); } @Async("myExecutor2") @Override public void doSomething2() { System.out.println(String.format("thread:%d **** doSomething2", Thread.currentThread().getId())); } }
配置
<context:component-scan base-package="cn.matt.schedule"/> <!-- 开启@Async注解支持 --> <task:annotation-driven executor="myExecutor1"/> <!-- 定义执行器 --> <task:executor id="myExecutor1" pool-size="10" /> <task:executor id="myExecutor2" pool-size="10" />
测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-context.xml") public class SpringTestBase {}
public class AsyncTest extends SpringTestBase { @Autowired private Hello hello; @Test public void test() { System.out.println(String.format("main thread:%d **** doSomething1", Thread.currentThread().getId())); hello.doSomething1(); hello.doSomething2(); } } // 输出: // main thread:1 **** doSomething1 // thread:20 **** doSomething2 // thread:19 **** doSomething1
说明:@Async默认使用<task:annotation-driven/>指定的执行器,当存在多个执行器时,可通过@Async的value属性单独指定
参考: