Java定时任务最详细讲解(普通项目,Spring项目)
文章目录
- 一.前言
- 二.普通项目
- 1.Timer
- 2.ScheduledExecutorService
- (1)scheduleAtFixedRate
- (2)scheduleWithFixedDelay.
- (3).对异常的处理
- 三.Spring项目.
- 1.Spring Task
- 2.结合@EnableAsync使用
- 四.总结
一.前言
定时任务在工作中可以说是最常见的需求了,比如定时发送邮件,读取配置,清除缓存等等,这一切都离不开定时任务,所以熟练的掌握定时任务使用及其注意点是非常有必要的,本文将从不同项目环境演示其对应定时任务的使用和注意点.
二.普通项目
1.Timer
在普通项目中,最常用的就是Timer了,它是Java自带的一种定时任务,可以很方便的进行使用.
在 java.util 包下,要跟 TimerTask 一起配合使用。
举例:
//设置定时任务TimerTask task = new TimerTask() {@Overridepublic void run() {// TODO Auto-generated method stubSystem.out.println("运行定时任务......");}};Timer timer = new Timer();//1秒后执行,然后每隔两秒执行一次.timer.schedule(task,1000L,2000L);
我们知道定时任务都是会启一个异步线程,去帮我们执行一些任务,那么在处理任何的定时任务时,一定要问自己两个问题:
- 如果某个定时任务超时了,是会帮我们再起一个线程去执行,还是会一直等当前任务执行完毕再去执行?
- 如果某个定时任务出现异常,会怎么处理?会不会影响其他任务的执行?
验证:
1.我们这里直接设定定时任务时间为3s,但是我们想让其每2s就执行一次定时任务,看看结果会怎么样
//设置定时任务TimerTask task = new TimerTask() {@Overridepublic void run() {//故意设定执行时间>间隔时间try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程:"+Thread.currentThread().getName()+",当前执行时间:"+System.currentTimeMillis()/1000);}};Timer timer = new Timer();//1秒后执行,然后每隔两秒执行一次.timer.schedule(task,1000L,2000L);
输出结果:
可以看到,每个定时任务间隔时间是有3s,说明Timer只有一个异步线程去同步执行定时任务,如果一个任务延迟,会影响到其他的任务.
2.直接让定时任务出现异常,看看会怎么样?
//设置定时任务TimerTask task = new TimerTask() {@Overridepublic void run() {//设置异常int i=2/0;System.out.println("线程:"+Thread.currentThread().getName()+",当前执行时间:"+System.currentTimeMillis()/1000);}};Timer timer = new Timer();//1秒后执行,然后每隔两秒执行一次.timer.schedule(task,1000L,2000L);
输出结果:
程序直接抛出异常,然后结束了,说明Timer对异常并没有进行任何的处理,直接向上抛出
结论:
- Timer中的定时任务只会启一个线程,也就是说如果某个任务因为一些异常原因延迟,将会影响到后面定时任务的执行.
- Timer中任一任务出现异常,会直接导致整个程序结束.
2.ScheduledExecutorService
在了解了Timer后,我们知道它有些不足,是单线程去运行的,所以JDK又推出了一种结合线程池的定时任务类.即ScheduledExecutorService,针对每个线程任务,会指定线程池中的一个线程去执行.是Timer的更好的一个替代品.
主要方法:
这里不再演示一次性任务schedule()的使用,主要演示scheduleAtFixedRate()和scheduleWithFixedDelay()
(1)scheduleAtFixedRate
以给定的间隔去执行任务,当任务的执行时间过长,超过间隔时间后,下一次定时任务的执行会顺延至当前定时任务执行完毕之后.否则严格按照间隔时间去执行.
举例:
*/public class MyFutureTask2 {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建一个线程池,以JVM可以利用的CPU数为核心线程数,并指定拒绝策略ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("当前任务执行失败" + r);}});// 在指定0秒延迟后执行,之后每两秒执行一次,此时任务指定时间是大于间隔时间的scheduledExecutor.scheduleAtFixedRate(new ThreadRunnable(), 0, 2, TimeUnit.SECONDS);}}class ThreadRunnable implements Runnable{@Overridepublic void run() {//设定执行时间>间隔时间System.out.println("线程:"+Thread.currentThread().getName()+",执行任务时间:"+ LocalDateTime.now());try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}}
输出结果:
可以看到,我们指定的任务间隔时间是2s,但是每一个任务要执行的花费时间为10s,这个时候定时任务就是顺延至当前任务执行完毕后,立即开始执行下一个任务.所以时间差值是10s.
(2)scheduleWithFixedDelay.
以给定的间隔时间执行任务,区别是这个间隔时间是从上一次定时任务执行完毕之后才开始算起的.
举例:
还是刚刚的场景,间隔2s,任务执行时间10s.
public class MyFutureTask2 {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建一个线程池,以JVM可以利用的CPU数为核心线程数,并指定拒绝策略ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("当前任务执行失败" + r);}});// 在指定1秒延迟后执行,之后每两秒执行一次,此时任务指定时间是大于间隔时间的scheduledExecutor.scheduleWithFixedDelay(new ThreadRunnable(), 0, 2, TimeUnit.SECONDS);}}class ThreadRunnable implements Runnable{@Overridepublic void run() {//设定执行时间>间隔时间System.out.println("线程:"+Thread.currentThread().getName()+",执行任务时间:"+ LocalDateTime.now());try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}}
输出结果:
可以看到,任务的实际间隔时间达到了12s,也就是 任务执行的时间+指定的间隔时间
从上面来看,ScheduledExecutorService是基于线程池实现的,相比Timer也有更强大的API,但是从上面输出结果看,其对于每个设定的定时任务其实还是同步执行的,区别是ScheduledExecutorService可以很方便的设置多个定时任务,如下图所示:
(3).对异常的处理
在Timer中,出现异常会导致整个程序终止,那么在ScheduledExecutorService中呢?
直接上代码演示:
这里定义了一个随机异常,当返回的布尔值为true时,程序出现异常.
public class MyFutureTask2 {public static void main(String[] args) throws ExecutionException, InterruptedException {//创建一个线程池,一JVM可以利用的CPU数为核心线程数,并指定线程工厂和拒绝策略ScheduledThreadPoolExecutor scheduledExecutor = new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("当前任务执行失败" + r);}});// 在指定0秒延迟后执行,之后每两秒执行一次scheduledExecutor.scheduleAtFixedRate(new ThreadRunnable(), 0, 2, TimeUnit.SECONDS);}
}class ThreadRunnable implements Runnable{@Overridepublic void run() {//设定随机异常Random random = new Random();boolean nextBoolean = random.nextBoolean();if(nextBoolean){System.out.println("程序开始出现异常...");int i=2/0;}System.out.println("线程:"+Thread.currentThread().getName()+",执行任务时间:"+ LocalDateTime.now());}
}
输出结果:
在程序出现异常后,没有任何打印信息,并且定时任务也不会继续执行,所以ScheduledExecutorService对异常的处理也并不好,我们甚至不知道出现了什么情况,所以在使用ScheduledExecutorService的时候,一定要做好异常的捕获,否则定时任务不执行都不知道出现了什么情况
三.Spring项目.
在Spring 项目中给了一种更加简单的方式去启动定时任务,我们这里以Springboot项目举例进行说明.
其实它底层是也基于 JDK 的 ScheduledExecutorService实现的.
1.Spring Task
使用步骤:
- 引入依赖.
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency>
- 配置类.
重点:使用 @EnableScheduling注解启动定时任务功能,并实现自定义线程池.
@Configuration//该注解可以启动TaskScheduling,实现SchedulingConfigurer是为了对你的定时任务有一些个性化的设置@EnableSchedulingpublic class TaskSchedulerConfig implements SchedulingConfigurer {@Overridepublic void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {//实现定时任务必须要有线程池,这里即传入一个线程池,是一个自定义方法,在下面实现scheduledTaskRegistrar.setScheduler(schedulerThreadPool());}//里面标注方法的意思就是在这个bean destroy的时候,调用ScheduledThreadPoolExecutor类的shutdown方法优雅的关闭线程池@Bean(destroyMethod = "shutdown")public ScheduledThreadPoolExecutor schedulerThreadPool() {/*ScheduledThreadPoolExecutor这个类是实现了定时任务的线程池Runtime类解析,每一个java运行程序都有一个Runtime类实例,使当前运行程序能够与运行环境相关联,getRuntime方法返回当前运行程序的Runtime对象,avaliableProcessors方法返回可用处理器的数目,用返回的处理器的数目充当corePoolSize*/return new ScheduledThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("当前任务执行失败"+r);}});}}
- 然后就可以使用定时任务了.
重点:使用@Scheduled注解真正开启一个定时任务.注意定时任务类一定要注入到Spring工厂中
@Componentpublic class Scheduler {/**初始化延迟3秒后执行, fixedDelay则和scheduleWithFixedDelay方法含义是一样的fixedRate和scheduleAtFixedRate方法含义是一样的*/@Scheduled(initialDelay = 3000,fixedDelay = 3000) public void execute(){System.out.println("定时任务执行.....");}@Scheduled(cron="0 10 23 20 10 7") //cron表达式,比较常用,最下面有详细用法.public void execute2(){System.out.println("定时任务执行2...");}}
这里多了一个可以使用cron表达式进行定时任务的配置,cron表达式可以定义更加灵活的定时配置,比如说周一到周五每天上午10:15执行任务,等等.如果不太了解的,可以参考这个讲解cron表达式
注意:Spring Task底层使用的是JDK的ScheduledExecutorService,所以它的每个定时任务一定也是同步执行的,也就是说当间隔时间达到之后,如果上一个定时任务没有执行完毕,会等待上一个定时任务执行完之后才开启下一个
此外,Spring Task是会抛出异常的,并且之后的定时任务还会继续执行,不会受到影响,这个是比较好的
2.结合@EnableAsync使用
如果我们的业务场景中,需要完全忽略任务的执行时间,假如上一个任务没有执行完毕的情况下,直接新启一个线程去执行,那么我们就可以结合@EnableAsync去使用.
举例:
(1).启用异步线程功能.增加@EnableAsync注解
(2).在定时任务的方法上增加@Async注解.
@Async@Scheduled(initialDelay = 1000,fixedRate = 2000)public void test() throws InterruptedException {System.out.println("线程:"+Thread.currentThread().getName()+",当前执行时间:"+ LocalTime.now());Thread.sleep(5000);}
输出结果:
可以看到,即使定时任务的执行时间是5s,间隔时间是2s,每次也都会新启一个线程去执行任务,但是要注意,使用此方式,会默认使用异步任务对应的默认线程池SimpleAsyncTaskExecutor,而不是定时任务中的线程池ScheduledThreadPoolExecutor,所以在此方式下,还需要自定义一下异步任务的线程池
@Beanpublic ThreadPoolTaskExecutor taskExecutor(){ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();taskExecutor.setCorePoolSize(10);taskExecutor.setMaxPoolSize(10);taskExecutor.setQueueCapacity(200);taskExecutor.setKeepAliveSeconds(60);taskExecutor.setThreadNamePrefix("自定义-");taskExecutor.setAwaitTerminationSeconds(60);//使用自定义拒绝策略,或者自带的几种拒绝策略taskExecutor.setRejectedExecutionHandler((runable,threadPoolExecutor)->{//TODO 自定义的拒绝策略System.out.println("当前任务执行失败:"+runable);});return taskExecutor;}
增加自定义异步线程池后的输出结果:
上面所说的定时任务其实只支持单点的定时任务,如果要使用分布式定时任务,可以使用业界比较主流的es-job.xxl-job等.es-job是使用ES做辅助处理,xxl-job则是使用Mysql做辅助处理,这里暂时不再详细说明,待补充博客.
四.总结
Java定时任务最详细讲解(普通项目,Spring项目)相关推荐
- Java语言基础详细讲解
就像人与人之间交流使用的语言需要遵循一定的语法规则一样,Java语言也离不开特定语法的支持,如基本语法.数据类型.变量.常量.运算符与表达式.类型转换和输入输出等,只不过这些语法要比日常生活中语言的语 ...
- java 定时任务怎么关闭_浅谈springboot项目中定时任务如何优雅退出
在一个springboot项目中需要跑定时任务处理批数据时,突然有个Kill命令或者一个Ctrl+C的命令,此时我们需要当批数据处理完毕后才允许定时任务关闭,也就是当定时任务结束时才允许Kill命令生 ...
- java循环输入_【图文+视频新手也友好】Java一维数组详细讲解(内含练习题答案+详解彩蛋喔~)...
[新手友好型视频+图文] 全面讲解Java一维数组(内含带答案和讲解的练习题彩蛋喔) 看完即上手!更有详解版练习题来帮你加深印象~~ 一.视频讲解 一维数组详解https://www.zhihu.co ...
- 【JAVA字符串最详细讲解】
JAVA字符串 一.String类 1.声明字符串 2.创建字符串 二.连接字符串 1.连接多个字符串 2.连接其他数据类型 三.获取字符串信息 1.获取字符串长度 2.字符串查找 3.获取指定索引位 ...
- Java split方法详细讲解
今天是圣诞节,我是中国人,无视圣诞节. 文章可能有点长,看下来必定有所收获. 没有学过正则表达式的去b站看,一个半小时应该可以看完,要看请点这里 这是必备的前置技能,不懂得话没法真正明白split用法 ...
- abnf java实现_详细讲解如何利用Java实现组合式解析器?
简介:Ward Cunningham 曾经说过,干净的代码清晰地表达了代码编写者所 想要表达的东西,而优美的代码则更进一步,优美的代码看起来就像是专门为了 要解决的问题而存在的.在本文中,我们将展示一 ...
- Java Set接口详细讲解 TreeSet的定制排序和自然排序
Set接口概述 Set接口是Collection的子接口,set接口没有提供额外的方法 Set 集合不允许包含相同的元素,如果试把两个相同的元素加入同一个Set 集合中,则添加操作失败. Set 判断 ...
- java 零拷贝详细讲解
文章目录 一.传统IO 二.零拷贝 1.通过DirectByteBuffer优化 2.通过 linux 2.1 sendFile优化 3.linux 2.4优化 三.java实现零拷贝 1.mmap ...
- java里throws详细讲解,基于Java中throw和throws的区别(详解)
系统自动抛出的异常 所有系统定义的编译和运行异常都可以由系统自动抛出,称为标准异常,并且 Java 强烈地要求应用程序进行完整的异常处理,给用户友好的提示,或者修正后使程序继续执行. 语句抛出的异常 ...
最新文章
- 【自考】信息系统开发与管理(二)——章节详读
- 创建Joomla菜单
- MFC的固高环形倒立摆GRIP2002实验平台
- JavaScript的求模、取整、小数的取舍
- Linux下 RPM 包和Deb包的安装(代码指令+案列)
- html瀑布流下拉刷新,瀑布流下拉刷新 - osc_1wnye1so的个人空间 - OSCHINA - 中文开源技术交流社区...
- java语言程序设计考题_《JAVA语言程序设计》期末考试试题及答案6(应考必备题库)...
- ENVI/IDL实现每个波段信噪比计算
- 计算机网络组成两大部分组成,计算机网络的组成部分
- 完全卸载chrome
- 成功三大定律:荷花定律、金蝉定律、竹子定律
- 51单片机课设--篮球计分器
- 11月YouTube全球视频点赞Top10 :碧梨、比伯、萌德、A妹神仙打架
- GPS 入门 8 —— GPS定位基本原理浅析
- HTTP请求转发那些事:你可能不知道的Hop-by-hop Headers和End-to-end Headers
- 华为计算机和备忘录不见了,华为手机桌面备忘录不见了该怎么办
- 华为某高管工资曝光:每月高达27万,众网友表示长了见识
- 使用LibreOffice将word转化为pdf -解决中文乱码
- (附源码)计算机毕业设计SSM在线党建学习平台
- Docker Hub的搭建、配置网络加速器、私有仓库的搭建以及私有仓库的认证与加密