Java中的定时任务

在Java中,主要有两种方式实现定时任务:

  • 使用java.util包中的Timer和TimerTask。
  • 使用Java并发包中的ScheduledExecutorService。

Timer和TimerTask

TimerTask:表示一个定时任务,它是一个抽象类,实现了Runnable,具体的定时任务需要继承该类,实现run方法。

Timer是一个具体类,它负责定时任务的调度和执行

运行一次:

//在指定绝对时间time运行任务task
public void schedule(TimerTask task, Date time)
//在当前时间延时delay毫秒后运行任务task
public void schedule(TimerTask task, long delay)

固定延时重复执行:

//第一次计划执行时间为firstTime,后一次的计划执行时间为前一次"计划"执行时间加上period
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
//同样是固定频率重复执行,第一次计划执行时间为当前时间加上delay
public void scheduleAtFixedRate(TimerTask task, long delay, long period)

固定延时(fixed-delay)与固定频率(fixed-rate)的区别:

  1. 对于固定延时,它是基于上次任务的“实际”执行时间来算的,如果由于某种原因,上次任务延时了,则本次任务也会延时,而固定频率会尽量补够运行次数。
  2. 如果第一次计划执行的时间firstTime是一个过去的时间,则任务会立即运行,对于固定延时的任务,下次任务会基于第一次执行时间计算,而对于固定频率的任务,则会从firstTime开始算,有可能加上period后还是一个过去时间,从而连续运行很多次,直到时间超过当前时间。

具体实现

Timer内部主要由任务队列和Timer线程两部分组成:

  • 任务队列是一个基于堆实现的优先级队列(数组实现的小顶堆TaskQueue),按照下次执行的时间排优先级。
  • Timer线程负责执行所有的定时任务,需要强调的是,一个Timer对象只有一个Timer线程。

Timer线程主体是一个循环,从队列中获取任务:

  • 如果队列中有任务且计划执行时间小于等于当前时间,就执行它;
  • 如果队列中没有任务或第一个任务延时还没到,就睡眠。如果睡眠过程中队列上添加了新任务且新任务是第一个任务,Timer线程会被唤醒,重新进行检查。

在执行任务之前,Timer线程判断任务是否为周期任务,如果是,就设置下次执行的时间并添加到优先级队列中。

  • 对于固定延时的任务,下次执行时间为当前时间加上period;
  • 对于固定频率的任务,下次执行时间为上次计划执行时间加上period。

注意:

  1. 下次任务的计划是在执行当前任务之前就做出了的
  2. 对于固定延时的任务,延时相对的是任务执行前的当前时间,而不是任务执行后,这与后面讲到的ScheduledExecutorService的固定延时计算方法是不同的,后者的计算方法更合乎一般的期望。对于固定频率的任务,延时相对的是最先的计划,所以,很有可能会出现考点一中的情况。
  3. 定时任务不能耗时太长,更不能是无限循环(其他任务可能永远没有机会执行)
  4. TimerTask的run方法内需要捕获所有异常:一旦run抛出异常,Timer线程就会退出,从而所有定时任务都会被取消。

ScheduledExecutorService

实践中建议使用ScheduledExecutorService,尽量避免使用Timer。

ScheduledExecutorService是一个接口:

public interface ScheduledExecutorService extends ExecutorService {
	//单次执行,在指定延时delay后运行command
	public ScheduledFuture<? > schedule(Runnable command, long delay, TimeUnit unit);
	//单次执行,在指定延时delay后运行callable
	public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
	//固定频率重复执行
	public ScheduledFuture<? > scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);
	//固定延时重复执行
	public ScheduledFuture<? > scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
}

对于固定频率的任务,第一次执行时间为initialDelay后,第二次为initialDelay+period,第三次为initial-Delay+2*period,以此类推。

对于固定延时的任务,从任务执行后开始算的,第一次为initialDelay后,第二次为第一次任务执行结束后再加上delay

ScheduledThreadPoolExecutor是ScheduledExecutorService的主要实现类,它是线程池ThreadPoolExecutor的子类,是基于线程池实现的。

它的任务队列是一个无界的优先级队列,所以最大线程数对它没有作用,即使core-PoolSize设为0,它也会至少运行一个线程。

工厂类Executors也提供了一些方便的方法,以方便创建ScheduledThreadPoolExecutor:

//单线程的定时任务执行服务
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory)
//多线程的定时任务执行服务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)

与Timer不同:

  1. 不支持以绝对时间作为首次运行的时间。
  2. 背后是线程池,可以有多个线程执行任务。
  3. 任务执行后再设置下次执行的时间,对于固定延时的任务更为合理。
  4. 任务执行线程会捕获任务执行过程中的所有异常,一个定时任务的异常不会影响其他定时任务,不过,发生异常的任务(即使是一个重复任务)不会再被调度。

#java原理##并发编程#
27届毕业生-Java知识专辑 文章被收录于专栏

知其然知其所以然,只有掌握了底层原理,借助第一性原理,才可以在日常开发和项目中运用自如,潇洒走江湖。 专为27届毕业生准备,托起您的就业梦。 该专辑会不定时更新,建议27届同学订阅,入职后扎实的基本功可以帮您争取更好的机会和项目。

全部评论
固定延时怎么算?
点赞 回复 分享
发布于 03-12 10:26 北京
学完定时任务,看下这小段代码有什么问题吗:https://www.nowcoder.com/discuss/859495471616634880
点赞 回复 分享
发布于 03-06 18:09 北京

相关推荐

昨天 00:04
已编辑
门头沟学院 Java
约面的挺突然。。狠下心接了1.自我介绍2.讲讲JAVA的反射3.可以继续讲讲AOP,动态代理[&nbsp;因为讲反射不小心吟唱到了例如AOP的动态代理,但是这块记忆的非常不熟,结果磕磕绊绊&nbsp;]4.项目我看你写了AOP和注解,具体怎么实现滑动窗口限流的[&nbsp;梦到什么说什么,吟唱八股发散千万不要散到自己不熟悉的区域&nbsp;]5.也讲讲为什么另一个项目选择令牌桶,具体流程6.&nbsp;OK,讲讲&nbsp;Redis&nbsp;的数据类型?还有吗?就了解这五种嘛[&nbsp;把5个的基础类型从应用对比到历届底层全都吟唱了一遍。一句还有吗直接没力气了,简历就写了理解5种,别的我是真一点没看TT&nbsp;]7.讲讲Redission分布式锁实现8.这个指数退避怎么实现的9.在这里有考虑去保障幂等性嘛10.这里为什么使用指数退避呢?&nbsp;什么时候用均匀重传[已经晕过去了说不了解,刚说了后就意识到,估计应该说指数退避能缓解压力防止下游服务器雪崩之类的]11.ok,那讲讲JMM12.讲讲RocketMQ如何保证的不丢消息13.讲讲RocketMQ延迟消息原理14.讲讲项目Redis实现会话记忆这一块15.如果ai调用function&nbsp;calling出现幻觉,有考虑怎么解决吗?[&nbsp;不了解,面试官说什么接口幂等化,高危操作人工防护,没在听,感觉人已经飞升了TT&nbsp;]16.mcp了解嘛?和function&nbsp;calling有什么区别[&nbsp;依旧不了解,只能说了个前者规范架构抽象解耦,后者耦合高只能算个工具调用]17.AI生成代码的代码质量怎么保障,那平时如何review的呢18.算法。lc215&nbsp;&nbsp;数组中最大第k个元素19.打算考研还是本科就业20.反问1️⃣有哪里不足,有哪些需要提高的部分。[主要说知识广度不够,多刷算法,让我别太紧张]2️⃣部门业务会做什么人生第二次面试。感觉大厂面试官的气场压力很大应该凉了不过这次面试非常锻炼心态,多面试,多面试。
点赞 评论 收藏
分享
评论
1
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务