JAVA实现定时任务的几种方式
@(JAVA)[spring|quartz|定时器] 近期项目开发中需要动态的添加定时任务,比如在某个活动结束时,自动生成获奖名单,导出excel等,此类任务由于活动时间是动态的,不能把定时任务配置在配置文件或写死在代码中。当然也可以增加一个定时扫描的任务来实现。借此机会整理了AVA实现定时任务的几种常用方式,以下做简要介绍。目前主要有以下几种实现方式:
JDK自带 :JDK自带的Timer以及JDK1.5+ 新增的ScheduledExecutorService;Quartz :简单却强大的JAVA作业调度框架Spring3.0以后自带的task :可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多;下面将一一介绍以上三种实现方式。[TOC]
JDK 自带的定时器实现Timer类这个类允许你调度一个java.util.TimerTask任务。主要有以下几个方法:schedule(TimerTask task, long delay) 延迟 delay 毫秒 执行public static void main(String[] args) { for (int i=0; i < 10; ++i) { new Timer("timer - " + i).schedule(new TimerTask() { @Override public void run() { println(Thread.currentThread().getName() + " run "); } }, 1000); } }out :timer - 2 run timer - 1 run timer - 0 run timer - 3 run timer - 9 run timer - 4 run timer - 8 run timer - 5 run timer - 6 run timer - 7 run
schedule(TimerTask task, Date time) 特定時間執行public static void main(String[] args) { for (int i=0; i < 10; ++i) { new Timer("timer - " + i).schedule(new TimerTask() { @Override public void run() { println(Thread.currentThread().getName() + " run "); } }, new Date(System.currentTimeMillis() + 2000)); } }out:timer - 0 run timer - 7 run timer - 6 run timer - 8 run timer - 3 run timer - 5 run timer - 2 run timer - 1 run timer - 4 run timer - 9 run
schedule(TimerTask task, long delay, long period) 延迟 delay 执行并每隔period 执行一次public static void main(String[] args) { for (int i=0; i < 10; ++i) { new Timer("timer - " + i).schedule(new TimerTask() { @Override public void run() { println(Thread.currentThread().getName() + " run "); } }, 2000 , 3000); } }out:timer - 0 run timer - 5 run timer - 4 run timer - 8 run timer - 3 run timer - 2 run timer - 1 run timer - 7 run timer - 9 run timer - 6 run timer - 3 run timer - 7 run timer - 5 run timer - 4 run timer - 8 run
ScheduledExecutorService 接口实现类ScheduledExecutorService 是JAVA 1.5 后新增的定时任务接口,主要有以下几个方法。- ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);- <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);- ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);- ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);
默认实现为ScheduledThreadPoolExecutor 继承了ThreadPoolExecutor 的线程池特性,配合future特性,比Timer更强大。 具体用法可以阅读JDK文档;spring Task内部也是依靠它实现的
。示例代码:
public static void main(String[] args) throws SchedulerException { ScheduledThreadPoolExecutor executor=(ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(10); for (int i=0; i < 10; ++i) { executor.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " run "); } } , 2 , TimeUnit.SECONDS); } executor.shutdown(); }out:pool-1-thread-2 run pool-1-thread-5 run pool-1-thread-4 run pool-1-thread-3 run pool-1-thread-8 run pool-1-thread-5 run pool-1-thread-7 run pool-1-thread-2 run pool-1-thread-1 run pool-1-thread-6 run
Quartz 定时器实现Quartz是一个完全由Java编写的开源作业调度框架,为在Java应用程序中进行作业调度提供了简单却强大的机制。Quartz允许开发人员根据时间间隔来调度作业。它实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。可以动态的添加删除定时任务,另外很好的支撑集群调度
。简单地创建一个org.quarz.Job接口的Java类,Job接口包含唯一的方法:
public void execute(JobExecutionContext context) throws JobExecutionException;
在Job接口实现类里面,添加需要的逻辑到execute()方法中。配置好Job实现类并设定好调度时间表(Trigger),Quartz就会自动在设定的时间调度作业执行execute()。
整合了Quartz的应用程序可以重用不同事件的作业,还可以为一个事件组合多个作业。Quartz通过属性文件来配置JDBC事务的数据源、全局作业、触发器侦听器、插件、线程池等等。(quartz.properties)
通过maven引入依赖(这里主要介绍2.3.0) 注意:shiro-scheduler中依赖的是1.x版本 如果同时使用会冲突
<!-- https://mvnrepository.com/artifact/org.quartz-scheduler/quartz --> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
创建Job类public class TestJob implements Job{ @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { println(Thread.currentThread().getName() + " test job begin " + DateUtil.getCurrentTimeStr()); }}
调度任务public static void main(String[] args) throws InterruptedException, SchedulerException { Scheduler scheduler=new StdSchedulerFactory().getScheduler(); // 开始 scheduler.start(); // job 唯一标识 test.test-1 JobKey jobKey=new JobKey("test" , "test-1"); JobDetail jobDetail=JobBuilder.newJob(TestJob.class).withIdentity(jobKey).build(); Trigger trigger=TriggerBuilder.newTrigger() .withIdentity("test" , "test") // 延迟一秒执行 .startAt(new Date(System.currentTimeMillis() + 1000)) // 每隔一秒执行 并一直重复 .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()) .build(); scheduler.scheduleJob(jobDetail , trigger); Thread.sleep(5000); // 删除job scheduler.deleteJob(jobKey); }out :DefaultQuartzScheduler_Worker-1test job begin 2017-06-03 14:30:33DefaultQuartzScheduler_Worker-2test job begin 2017-06-03 14:30:34DefaultQuartzScheduler_Worker-3test job begin 2017-06-03 14:30:35DefaultQuartzScheduler_Worker-4test job begin 2017-06-03 14:30:36DefaultQuartzScheduler_Worker-5test job begin 2017-06-03 14:30:37
Quartz 主要包含以下几个部分
Job:是一个接口,只有一个方法void execute(JobExecutionContext
context),开发者实现该接口定义运行任务,JobExecutionContext类提供了调度上下文的各种信息。Job运行时的信息保存在JobDataMap实例中;
JobDetail:Quartz在每次执行Job时,都重新创建一个Job实例,所以它不直接接受一个Job的实例,相反它接收一个Job实现类,以便运行时通过newInstance()的反射机制实例化Job。因此需要通过一个类来描述Job的实现类及其它相关的静态信息,如Job名字、描述、关联监听器等信息,JobDetail承担了这一角色。
Trigger:是一个类,描述触发Job执行的时间触发规则。主要有SimpleTrigger和CronTrigger这两个子类。当仅需触发一次或者以固定时间间隔周期执行,SimpleTrigger是最适合的选择;而CronTrigger则可以通过Cron表达式定义出各种复杂时间规则的调度方案:如每早晨9:00执行,周一、周三、周五下午5:00执行等;
Calendar:org.quartz.Calendar和java.util.Calendar不同,它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合——java.util.Calendar代表一个日历时间点,无特殊说明后面的Calendar即指org.quartz.Calendar)。一个Trigger可以和多个Calendar关联,以便排除或包含某些时间点。假设,我们安排每周星期一早上10:00执行任务,但是如果碰到法定的节日,任务则不执行,这时就需要在Trigger触发机制的基础上使用Calendar进行定点排除。
Scheduler:代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。
Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。可以通过SchedulerFactory创建一个Scheduler实例。Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler#
getContext()获取对应的SchedulerContext实例;
ThreadPool:Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。
关于简单使用,可以参考quartz的example,下面链接是一些入门帮助。
Quartz定时任务学习(一)简单任务 Quartz定时任务学习(二)web应用 Quartz定时任务学习(三)属性文件和jar
深入学习可以阅读官方文档和相关博客阅读
以下为推荐博客地址quartz详解2:quartz由浅入深
TaskScheduler
接口的几个实现类实现。删除和修改任务比较麻烦。
主要用法有以下三种:Spring配置文件实现注解实现代码动态添加配置文件实现
spring-schedule.xml<task:scheduler id="myScheduler" pool-size="10" /><task:scheduled-tasks scheduler="myScheduler"> <task:scheduled ref="job" method="test" cron="0 * * * * ?"/></task:scheduled-tasks>
注解实现
spring-schedule.xml<task:scheduler id="myScheduler" pool-size="10" />// 启用注解<task:annotation-driven scheduler="myScheduler"/>
@Component public class Task{ @Scheduled(cron="0/5 * * * * ? ") //每5秒执行一次 public void execute(){ DateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(DateTime.now().toDate())+"*********B任务每5秒执行一次进入测试"); } }
代码动态添加
spring-schedule.xml<bean id="myScheduler" class="d179-578c-f641-885c org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler"> <property name="poolSize" value="10"/> <property name="threadGroupName" value="myScheduler" /> <property name="threadNamePrefix" value="-1" /></bean><task:annotation-driven scheduler="myScheduler"/>
@Componentpublic class Test { @Autowired private ThreadPoolTaskScheduler myScheduler; public void addJob(){ myScheduler.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + " run "); } } , new CronTrigger("0/5 * * * * ? ")); //每5秒执行一次 }}
spring 结合 quartz 实现任务调度spring 配置文件 spring-quartz.xml<bean id="quartzsScheduler" class="8a23-d903-189c-2adb org.springframework.scheduling.quartz.SchedulerFactoryBean" lazy-init="false"> <property name="triggers"> <list> <ref bean="testTrigger" /> </list> </property></bean><!-- jobClass需要继承QuartzJobBean 也可以使用 MethodInvokingJobDetailFactoryBean 定义任意类任意方法为Job--><bean id="testJobDetail" class="d903-189c-2adb-650b org.springframework.scheduling.quartz.JobDetailFactoryBean"> <property name="jobClass"> <value>com.test.TestJob</value> </property> <property name="durability" value="true" /> <!-- requestsRecovery属性必须设置为 true,当Quartz服务被中止后,再次启动或集群中其他机器接手任务时会尝试恢复执行之前未完成的所有任务 --> <property name="requestsRecovery" value="true" /></bean><bean id="testTrigger" class="189c-2adb-650b-e877 org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail" ref="testJobDetail" /> <property name="cronExpression" value="0 0 10 * * ?" /></bean>
动态增加删除
@Componentpublic class Test { @Autowired private SchedulerFactoryBean quartzScheduler; public void addJob() throws SchedulerException { Scheduler scheduler=quartzScheduler.getScheduler(); JobKey jobKey=new JobKey("test", "test"); if (scheduler.checkExists(jobKey)) { return; } JobDetail jobDetail=JobBuilder.newJob(TestJob.class).withIdentity(jobKey).build(); Trigger trigger=TriggerBuilder.newTrigger().withIdentity("test", "test") .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()).build(); scheduler.scheduleJob(jobDetail, trigger); }}
以上仅仅是对自己学习的总结,深入了解还需查找相关资料。比如动态增加,修改定时任务。以及Quartz的集群模式等。
配图是的,不用任何框架,用我们朴素的 Java 编程语言就能实现定时任务。
今天,栈长就介绍 3 种实现方法,教你如何使用 JDK 实现定时任务!
1、 sleep
这也是我们最常用的 sleep 休眠大法,不只是当作休眠用,我们还可以利用它很轻松的能实现一个简单的定时任务。
实现逻辑:
新开一个线程,添加一个 for/ while 死循环,然后在死循环里面添加一个 sleep 休眠逻辑,让程序每隔 N 秒休眠再执行一次,这样就达到了一个简单定时任务的效果。
实现代码如下:
/** * 休眠实现定时任务 * 来源:Java技术栈 */private static void sleepTask() { new Thread(() -> { while (true) { System.out.println("hi, 欢迎关注:Java技术栈"); try { // 每隔3秒执行一次 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();}
这种方式比较傻瓜化了,只能按固定频率运行,不能指定具体运行的时间。
另外,上面的箭头语法,栈长使用了 JDK 8 中的 Lambda 表达式,这里就不再撰述了,Java 8 系列实战教程我都写了一堆了,不清楚的可以关注Java技术栈阅读,我都整理好了。
2、Timer
来看下 JDK 自带的 java.util.Timer 类:
JDK 1.3 就内置了 java.util.Timer 类,可以用来调度 java.util.TimerTask 任务。
几个重要的方法:
schedule:开始调度任务,提供了几个包装方法;cancle:终止任务调度,取消当前调度的所有任务,正在运行的任务不受影响;purge:从任务队列中移除所有已取消的任务;另外,java.util.TimerTask 就是实现了 Runnable 接口,具体任务逻辑则是在 run 方法里去实现。
实现代码如下:
/** * timer定时任务 * 来源:Java技术栈 */private static void timerTask() throws InterruptedException { Timer timer=new Timer(); TimerTask timerTask=new TimerTask() { @Override public void run() { System.out.println("hi, 欢迎关注:Java技术栈"); } }; // 第一次任务延迟时间 long delay=2000; // 任务执行频率 long period=3 * 1000; // 开始调度 timer.schedule(timerTask, delay, period); // 指定首次运行时间// timer.schedule(timerTask, DateUtils.addSeconds(new Date(), 5), period); Thread.sleep(20000); // 终止并移除任务 timer.cancel(); timer.purge();}
这种实现方式比较简单,可以指定首次执行的延迟时间、首次执行的具体日期时间,以及执行频率,能满足日常需要。
另外,需要注意的是,Timer 是线程安全的,因为背后是单线程在执行所有任务。
Timer 也会有一些缺陷:
Timer 是单线程的,假如有任务 A,B,C,任务 A 如果执行时间比较长,那么就会影响任务 B,C 的启动和执行时间,如果 B,C 执行时间也比较长,那就会相互影响;Timer 不会捕获异常,如果 A,B,C 任何一个任务在执行过程中发生异常,就会导致 TImer 整个定时任务停止工作;Timer 是基于绝对时间调度的,而不是基于相对时间,所以它对系统时间的改变非常敏感;所以,如果在使用 Timer 的过程中要注意这些缺陷,虽然可以用,但不推荐。
3、ScheduledExecutorService
因 Timer 有一些缺陷,所以不太建议使用 Timer,推荐使用 ScheduledExecutorService:
ScheduledExecutorService 即是 Timer 的替代者,JDK 1.5 并发包引入,是基于线程池设计的定时任务类:
java.util.concurrent.Executors.newScheduledThreadPool
上了线程池,每个调度任务都会分配到线程池中的某一个线程去执行,任务就是并发调度执行的,任务之间互不影响。
几个重要的调度方法:
schedule:只执行一次调度;scheduleAtFixedRate:按固定频率调度,如果执行时间过长,下一次调度会延迟,不会同时执行;scheduleWithFixedDelay:延迟调度,上一次执行完再加上延迟时间后执行;
另外,可以看出,任务是支持 Runnable 和 Callable 调度的。
实现代码如下:
/** * 线程池定时任务 * 来源:Java技术栈 */public static void poolTask(){ ScheduledExecutorService pool=Executors.newScheduledThreadPool(10); pool.scheduleAtFixedRate(() -> { System.out.println("hi, 欢迎关注:Java技术栈"); }, 2000, 3000, TimeUnit.MILLISECONDS);}
这是一个按固定频率调度的任务,创建了 10 个核心线程数,首次执行延迟 2 秒,后续每 3 秒执行一次。
这种方式简单、好用,避免了使用 Timer 带来的各种问题,推荐使用这种实现方式。
总结
好了,本文栈长分享了 3 种 Java 实现定时任务的方式,也相对简单,但执行频率时间设置都太简单,只适合简单的业务,不适合实际复杂业务的需求,实际业务要考虑分布式、故障转移恢复等远要复杂的多。
本文仅给大家一个参考吧,在不用框架的前提下也能实现定时任务,在小而美的场景,还是很香的。
最后,Java 系列教程还会继续更新,关注Java技术栈第一时间推送,都是干货。
本节教程所有实战源码已上传到这个仓库:
https://github.com/javastacks/javastack
最后,觉得我的文章对你用收获的话,动动小手,给个在看、转发,原创不易,栈长需要你的鼓励。
版权申明:本文系 "Java技术栈" 原创,原创实属不易,转载、引用本文内容请注明出处,禁止抄袭、洗稿,请自重,尊重他人劳动成果和知识产权。
上一篇:phd和dba有什么不同
下一篇:XP桌面主题
发表评论