转自:简书作者:Fooisart
https://www.jianshu.com/p/e21eb60a2c41
jdk中实现定时器功能大致有三种方式:
java.util.Timer
Java . util . concurrent . Delayqueue
Java . util . concurrent . scheduledthreadpoolexecutor
静下心来,我们一个一个去探索。
I. java.util.Timer
示例代码:
/**
*安排指定的任务在指定的时间第一次开始重复执行固定速率周期
*每天中午12点
*
* @ authorFooisart
*创建于2019年1月14日21:46
*/
publicclassTimerDemo{
public static void main(String[]args){
Timer timer = newTimer
calendar calendar = calendar . getinstance;
日历。设置(日历。HOUR_OF_DAY,12);//控制时间
日历。设置(日历。MINUTE,0);//控制分钟
日历。设置(日历。SECOND,0);//控制秒
日期时间= calendar.getTime//执行时间为12:00:00
//每天12:00,每2秒执行一次操作
timer.schedule( newTimerTask {
@覆盖
publicvoidrun{
通过System.out.println( newDate+”执行任务。。。");
}
},时间,1000 * 2);
}
}
Demo中使用Timer实现一个定时任务,每天12点开始,每2秒执行一次。
偷换:在看源代码的时候偶然发现Timer里面有schedule和scheduleAtFixedRate,两者都可以按照指定的时间间隔在约定的时间执行。但是两者有什么区别呢?官方解释:一个是固定延迟,另一个是固定速率。那么这两个字到底是什么意思呢?在demo里运行一次代码,然后把调度改成scheduleAtFixedRate,就清楚了。
示例代码简洁明了,可以看出控制执行时间的方法应该是timer.schedule跟进看源码:
publicvoidschedule(TimerTask任务,日期优先时间,长周期){
if(句号& lt= 0)
throwNewillegalargumentexception(“非正周期”));
sched(task,firstTime.getTime,-period);
}
任务表示要执行的任务逻辑
FirstTime表示首次执行的时间
周期表示每次的间隔时间
继续跟进:
privatevoidsched(TimerTask任务,longtime,longperiod) {
//省略非键码
同步(队列){
if(!thread.newTasksMayBeScheduled)
thrownewIllegalStateException(“计时器已取消”));
同步(task.lock) {
if(task.state!= TimerTask。VIRGIN)
thrownewIllegalStateException(
“任务已经计划或取消”);
task.nextExecutionTime = time
task.period = period
task.state = TimerTask。SCHEDULED
}
queue . add(task);
if(queue.getMin == task)
queue.notify
}
}
这里实际上做了两件事
给task设定了一些参数,类似于初始化task。这里还给它加了把锁,可以思考一下为甚要在此初始化?为何要加锁?(不是本文范畴,各位伙伴自行思考)把初始化后的task加入到queue中。看完这个,我们还是不明白怎么实现计时。别担心,继续。输入queu.add(任务)
/**
*向优先级队列添加新任务。
*/
voidadd(TimerTask任务){
//如有必要,增加后备存储
if(size + 1== queue.length)
queue = Arrays.copyOf(queue,2 * queue . length);
queue[++ size]= task;
fixUp(大小);
}
注意这里提到的,给优先级队列添加一个新任务。实际上,这里的TimerTask[]是一个优先级队列,使用阵列存储模式。并且它的数据结构是堆。包括fixUp,我们可以看到它保留了堆属性,即heapify。
所以,分析了所有可以分析的东西之后,还是看不出时机是如何实现的。再静下心来想想。如果要执行定时任务,必须先启动定时器。所以我们再来关注一下施工方法。
计时器有四种构造方法,最下面一种是:
publicTimer(字符串名称){
thread.setName(名称);
thread.start
}
可以看到这里启动了一个thread,所以既然是Thread,就要注意它的run方法。输入:
publicvoidrun{
尝试{
mainLoop
}最后{
//有人杀了这个线程,表现得好像定时器取消了一样
同步(队列){
newTasksMayBeScheduled = false
queue.clear//删除过时的引用
}
}
}
继续进入主回路:
/**
*主定时器循环。(见类评论。)
*/
privateVirtumainLoop {
while( true) {
尝试{
TimerTask任务;
booleantaskFired
同步(队列){
//省略
longcurrentTime,executionTime
task = queue.getMin
同步(task.lock) {
if(task.state == TimerTask。取消){
queue.removeMin
继续;//不需要任何操作,再次轮询队列
}
current time = system . current timemillis;
execution time = task . next execution time;
if(taskFired =(execution time & lt;=currentTime)) {
如果(task.period == 0) { //非重复,则删除
queue.removeMin
task.state = TimerTask。已执行;
}其他{ //重复任务,重新计划
queue.rescheduleMin(
task.period & lt0?当前时间-任务。期间
:execution time+task . period);
}
}
}
if(!Task fried)//任务尚未激发;等待
queue . wait(execution time-CurrentTime);
}
if(TaskFried)//任务已触发;运行它,没有锁
task.run
} catch(中断异常e) {
}
}
}
从上面的源代码可以看出,有两个重要的if
if(taskFired =(execution time & lt;=currentTime)),表示执行时间已经到了,那么执行下面的任务就好了;
if(!TaskFired),表示不是执行的时候,等一下就好。那你怎么等?仔细看了一遍,原来是Object.wait(长超时)被调用了。
这里我们知道jdk中的定时器是这样实现的,等待是用最简单的Object实现的,等等!别担心,这里有一个小问题:用Therad.sleep可以实现吗?如果可以,为什么不可以?
Java . util . concurrent . Delayqueue
详细分析了Java.util.Timer,DelayQueue类似。整理一下,重新开始。
首先是示例代码:
延迟队列本质上是一个队列,只有存储延迟的子类在这个队列中才有意义,它们都定义了延迟任务:
publicClassDelayTaskImplatesdelated {
私有日期开始日期=新日期;
publicDelayTask(长延迟数百万){
this . startdate . set time(new date . gettime+delay millons);
}
@覆盖
publicintcompareTo(延迟o){
long result = this . GetDelay(time UNit。NANCES)
- o.getDelay(TimeUnit。NANCES);
if(结果& lt0) {
return-1;
} else if(result & gt;0) {
返回1;
} else{
返回0;
}
}
@覆盖
publiclonggetDelay(时间单位单位){
Date now = newDate
long diff = startdate . GetTime-now . GetTime;
returnunit.convert(diff,TimeUnit。毫秒);
}
}
public static void main(String[]args)throwsException {
阻塞队列& ltDelayTask>。queue = newDelayQueue & lt>。;
delay task delay task = new delay task(1000 * 5L);
queue . put(DelayTask);
while(queue.size >;0){
queue.take
}
}
看主要方法,我主要做了三件事:
构造DelayTask,其中的延迟时间是5秒将任务放入队列从队列中取任务延迟队列类似于计时器。刚才的TaskQueue,都是优先级队列。当元素被放入时,它们必须被堆积起来(如果元素已满,DelayQueue.put将阻塞)。自我研究)。专注于排队。拿着。
publie TakeTowsintErruptedexception {
finallyentrantlock lock = this . lock;
lock . Lock断续地;
尝试{
for(;;) {
e first = q . peek;
if(first == null)
可用。等待;
else{
long delay = first . GetDelay(NANCES);
if(延迟& lt= 0)
returnq.poll
first = null//等待时不要保留引用
if(leader!= null)
可用。等待;
else{
thread thithread = thread . current thread;
leader = thisThread
尝试{
available.awaitNanos(延迟);
}最后{
if(leader = = thithread)
leader = null
}
}
}
}
}最后{
if(leader = = null & amp;& ampq.peek!= null)
available.signal
lock.unlock
}
}
等待在源代码中出现三次:
第一次是队列为空时,等待;
等待第二次的原因是发现一个任务,执行时间没到,有领导准备执行。还是讲道理吧。既然有人准备执行,那我们就等着吧。
第三次是真正延迟的地方,可用的. await nano(delay)。此时没有其他线程执行,也就是我来执行,就等剩下的延迟时间吧。
在这里,我们理解延迟队列是通过条件等待来等待的。请注意,这里还有一个小问题:对象等待和条件等待之间有什么异同?
Java . util . concurrent . scheduledthreadpoolexecutor
由于ScheduledThreadPoolExecutor涉及到很多ThreadPoolExecutor,就不一一详细分析了,看完这个难免会累。直接总结结论:创建ScheduledThreadPoolExecutor时,线程池的工作队列使用DelayedWorkQueue,其take方法与DelayQueue.take方法非常相似,也有三个等待。
至此,到最后。总而言之,有两种方法可以在jdk中实现计时器:
使用Object.wait使用条件等待
记住文章中的两个小问题:
可以用Thread.sleep吗?如果可以,为什么不可以?
Object.wait和Conditon.await有什么异同?
1.《classtimer JDK 中定时器是如何实现的》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《classtimer JDK 中定时器是如何实现的》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/junshi/1191050.html