定时任务,在平常工做中,能够说是一个算是一个常见的需求场景,好比定时数据校验,数据报表输出,报警等java
前面一篇博文《Java并发学习之四种线程建立方式的实现与对比》, 有朋友指出线程池的方式应该算不上新的方式,而应该把Timer方式建立线程加上spring
这个倒是我我的见识不够,写的时候没有想到Timer这种场景了,因此说分享学习记录,不只仅能够帮助别人,本身也会所以收益并发
感谢@超大小龙虾 的指正,同时欢迎各位大侠对小弟多多指教框架
这里给出几种我的接触过的定时任务使用方式(不全,仅供你们参考)异步
Thread.sleep()
,休眠挂起线程,等待一段时间后再执行Executors.newScheduledThreadPool()
实现定时任务下面简单介绍上面的几种思路,以及通常的使用姿式ide
严格来说,这种不太可以算入定时任务的范畴,为何这么说?工具
通常咱们所说的定时任务能够区分为两种,一种是到了某个点自动执行;另外一种就是每隔多长时间执行一次学习
而这种线程Sleep的方式,则是在运行后,强制使线程进入阻塞状态一段时间,而后再执行后续的逻辑,通常的使用流程是.net
// 提早的业务逻辑 xxx try { Thread.sleep(1000); // 睡眠1s } catch(Exception e) { // .... } // 定时任务的业务逻辑 // xxx
这里把这个也放在定时任务里,能够看下面结合实例的case中的演示,利用这个sleep也能够很是猥琐的实现定时需求线程
TimerTask 是一个实现 Runnable的抽象类,所以能够将须要定时处理的业务逻辑封装在这个Task里面;而后经过Timer封装类来定时调度
TimerTask的使用姿式和通常的Runnable
接口没啥两样
通常使用姿式以下
// 建立timer实例 Timer timer = new Timer("demo); // 撰写定时任务逻辑 TimerTask task = new TimerTask() { @Override public void run() { System.out.println("timerTask: " + System.currentTimeMillis()); } }; // 定时调度执行 // 1. 100ms后开始执行task任务 timer.schedule(task, 100); // 2. 100ms后,首次执行,而且每隔100ms执行一次task任务 timer.scheduleAtFixedRate(task, 100, 100);
这个就有意思一点了,能够支持定时执行,也能够支持按一个频率执行,且通常使用能够将上面的步骤进行缩减, 直接这么玩
new Timer("schedule").schedule(new TimerTask() { @Override public void run() { System.out.println("timerTask: " + System.currentTimeMillis()); } }, 100);
Executors#newScheduledThreadPool
线程池方式
Executors
提供了一批建立线程池的方式,除了常见的建立固定大小的线程池以外,还有个一就是建立ScheduledExecutorService
来实现定时任务调度
借助Executors#newScheduledThreadPool
来实现定时任务很是简单
// 获取线程池 ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); // 延迟100ms后,执行定时任务 executorService.schedule(new Runnable() { @Override public void run() { System.out.println("task: " + System.currentTimeMillis()); } }, 100, TimeUnit.MILLISECONDS); // 100ms后,首次执行,而后每一个100ms执行一次 executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println("task: " + System.currentTimeMillis()); } }, 100, 100, TimeUnit.MILLISECONDS)
从使用姿式来看,和Timer方式差不离,一样支持定时执行与每隔多长时间执行两种方式
spring方式就很是强大了,并且支持注解的配置方式,配置完毕,而后在方法上加一个注解,就能够实现定时执行了
常见的使用姿式
// 100ms后执行 @Scheduled(fixedDelay = 100) public void doSomething() { // something that should execute periodically } // 每隔100ms执行一次 @Scheduled(fixedRate = 100) public void doSomething() { // something that should execute periodically }
并且比较厉害的是,这个还支持cron表达式
来两个实际的应用场景,用上面的四种方式分别实现
case:
系统中有一些统计数据,须要离线计算,天天凌晨计算完以后导入,而后须要一个定时任务,假设凌晨5点数据导入完毕;在5:15分进行校验,判断数据是否正常导入;校验完成以后,45分钟后即六点,将校验结果通知给owner
采用sleep的方式实现定时任务,由于其自己不支持定时的状况,因此就只能比较猥琐的计算须要sleep的时间了
实现代码以下(非精确的实现方式,主要为了演示如何用sleep来实现上面这种场景)
public class SleepDemo { static class Task implements Runnable { public void run() { while (true) { Calendar calendar = Calendar.getInstance(); int hour = calendar.get(Calendar.HOUR_OF_DAY); int min = calendar.get(Calendar.MINUTE); int sleepHour, sleepMin; // 计算sleep的小时数; 若在启动时,在五点前 5-hour; 不然须要加一天 sleepHour = 5 - hour < 0 ? (24 + 5 - hour) : (5 - hour); sleepMin = 15 - min; // 计算sleep的分钟数 try { long sleepTime = ((sleepHour * 60) + sleepMin) * 60 * 1000L; Thread.sleep(sleepTime); // 开始校验数据是否存在 System.out.println("数据校验"); // 等待到6点,开始报警 int second = calendar.get(Calendar.SECOND); sleepTime = ((59 - calendar.get(Calendar.MINUTE)) * 60 + 60 - second) * 1000L; Thread.sleep(sleepTime); System.out.println("开始报警"); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { new Thread(new Task(),"sleepDemo").start(); } }
简单说明下上面的实现思路:
while(true)
死循环,来实现每隔多长时间来执行一次这个实现方式,虽然说能够完成目标,可是很是的不优雅,下面来看下Timer的实现方式
使用Timer,须要借助TimerTask类,在其中书写定时任务的逻辑,由于case中有一个每隔一天跑一次的定时任务和一个延迟任务,因此这里用到了Timer的两种定时任务使用方式
public class TimerDemo { static class Task extends TimerTask { @Override public void run() { System.out.println("开始执行任务"); // 执行完毕,等待到6点发送报警 int min = Calendar.getInstance().get(Calendar.MINUTE); int sec = Calendar.getInstance().get(Calendar.SECOND); long delayTime = ((59 - min) * 60 + 60 - sec) * 1000L; new Timer().schedule(new TimerTask() { @Override public void run() { System.out.println("报警"); } }, delayTime); } } public static void main(String[] args) { Date date = new Date(); if (date.getHours() == 5 && date.getMinutes() > 15 || date.getHours() > 5) { date.setHours(5); } else { date.setMinutes(15); } date.setSeconds(0); new Timer().scheduleAtFixedRate(new Task(), date, 24 * 2600 * 1000L); } }
相比与上一个,稍微好了那么一丢丢,至少从代码结构上来看简洁了不少
定时任务的方式,用起来和前面差很少,依然是两种方式的混搭
public class ScheduleDemo { static ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2); static class Task extends TimerTask { @Override public void run() { System.out.println("开始执行任务"); // 执行完毕,等待到6点发送报警 int min = Calendar.getInstance().get(Calendar.MINUTE); int sec = Calendar.getInstance().get(Calendar.SECOND); long delayTime = (59 - min) * 60 + 60 - sec; executorService.schedule(() -> System.out.println("报警"), delayTime, TimeUnit.SECONDS); } } public static void main(String[] args) { Calendar calendar = Calendar.getInstance(); // 计算sleep的小时数 int sleepHour = 5 - calendar.get(Calendar.HOUR_OF_DAY); if(sleepHour < 0) { // 算下一天 sleepHour = 24 + sleepHour; } // 计算sleep的分钟数 int sleepMin = 15 - calendar.get(Calendar.MINUTE); long sleepTime = ((sleepHour * 60) + sleepMin) * 60 * 1000L; executorService.scheduleAtFixedRate(new Task(), sleepTime, 24 * 3600, TimeUnit.SECONDS); } }
与Timer在用法上不一样的一个是这里能够指定延迟的时间单位;可是但愿在指定的时间进行执行时,依然仍是得计算初始的延迟时间,和sleep使用方式中差很少
上面三中,是jdk自己就支持的定时任务的支持;总得来讲,能实现你的需求场景,可是很差用,还得让本身去计算delayTime/sleepTime;讲道理,这对使用者而言,实在是不能更不友好了;
可是在另外一方面,若延迟时间比较容易确认的话;或者单纯的使用每隔多长时间调度一次的话,Timer
和ScheduledExecutorService
两种方式都还不错
Spring 相比较jdk自带的几种方式而言,我认为,最完美的有两点
配置xml文件
<task:executor id="executor" pool-size="5" /> <task:scheduler id="scheduler" pool-size="10" /> <task:annotation-driven executor="executor" scheduler="scheduler" />
实现业务逻辑
@Component public class ScheduleDemo { @Scheduled(cron = "0 0 5 * * ?") public void doSome() { System.out.println(" 校验: " + System.currentTimeMillis()); } @Scheduled(cron = "0 0 6 * * ?") public void alarm(){ System.out.println(" 报警: " + System.currentTimeMillis()); } }
这个实现就简单了,相比较上面而言,添加一个注解,里面配置cron表达式,xml配置下,就能够实现定时任务
方式 | 说明 | 特色 |
---|---|---|
Thread#sleep | 线程挂起一段时间 | 经过定时目标与当前时间计算sleepTime,来强制实现定时任务 |
Timer#TimerTask | 异步定时任务 | TimerTask内部实现定时任务逻辑 <br/>1. Timer可按频率调度任务 <br/> 2. Timer也支持指定时间调度任务 |
ScheduledExecutorService | 计划任务线程池 | 1. 利用Executors#newScheduledThreadPool;建立线程池 <br/> 2. 建立线程任务实现定时任务逻辑 <br/> 3. 提交线程池执行,支持按频率调度,支持延迟多长时间调度 <br/> 4. 支持获取返回值 |
Spring Schedule | spring提供的定时任务 | 支持cron表达式,使用简单,很是简单,超级简单 |
不推荐使用 Thread#sleep的方式作定时任务
如指向利用jdk实现定时任务,能够考虑 Timer
和 ScheduledExecutorService
如项目自己就利用到了Spring,能够优先考虑这些优秀的框架提供的服务,用起来特别爽,谁用谁知道
尽信书则不如,已上内容,纯属一家之言,因本人能力通常,见识有限,若有问题,请不吝指正,感激