当前位置: 首页 > 后端技术 > Java

JavaTimer介绍

时间:2023-04-02 09:27:29 Java

java.util包提供了对定时任务的支持,涉及2个类:该任务执行代码,并使用Timer计时器来调度TimerTask。写一个任务:TimerTasktask=newTimerTask(){@Overridepublicvoidrun(){System.out.println(DateUtil.formatNow()+""+Thread.currentThread().getName()+"taskrun");}};然后使用Timer调度TimerTask,Timer提供了多种方法,分为一次性任务和可重复任务。1.一次性任务一次性任务是指Timer执行一次后,后续不再执行该任务。一次性任务包括2个方法,如下:voidschedule(TimerTasktask,longdelay):延迟毫秒后执行任务voidschedule(TimerTasktask,Datetime):在指定时间执行任务,如果时间过期,会立即执行2.可重复任务可重复任务是指允许任务按照设定的规则重复执行。可重复任务有4种方法,分为固定延迟schedule和固定速率scheduleAtFixedRate:voidschedule(TimerTasktask,longdelay,longperiod):延迟delay毫秒后执行task,然后每隔period毫秒执行task。voidschedule(TimerTasktask,DatefirstTime,longperiod):在指定时间执行一次任务,之后每隔period毫秒执行一次任务。ExecuteataskvoidscheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod):在指定的时间执行一个任务,然后每隔period毫秒执行一次任务示例1:schedule方法,延迟delay毫秒后执行任务,然后每周期毫秒执行一次taskSystem.out.println("startedat:"+DateUtil.formatNow());计时器timer=newTimer("计时器");timer.schedule(任务,1000,2000);输出:开始于:2022-10-3110:05:152022-10-3110:05:16定时器任务运行2022-10-3110:05:18定时器任务运行2022-10-3110:05:20定时任务运行示例2:schedulein在指定时间执行一个任务,然后每period毫秒执行一个任务System.out.println("Startedat:"+DateUtil.formatNow());计时器timer=newTimer("timer");timer.schedule(task,DateUtil.parse("2022-10-3110:07:00",DateUtil.YYYY_MM_DD_HH24_MM_SS),2000);输出:开始于:2022-10-3110:06:392022-10-3110:07:00定时器任务运行2022-10-3110:07:02定时器任务运行2022-10-3110:07:04定时器任务运行固定延迟调度和固定速率scheduleAtFixedRate在正常情况下看起来功能基本相同,不同的是当任务耗时超过执行时间间隔周期而延迟后续任务时,处理schedule和scheduleAtFixedRate的方法不同。下面三,fixeddelay和fixedrate的区别(重点)1.简介由于Timer内部只维护了一个线程来执行所有的任务,所以当前任务耗时过长,可能会导致后面的任务执行失败延迟。在任务延迟的情况下,fixed-delayschedule和fixed-ratescheduleAtFixedRate的区别在于schedule会被推迟,而scheduleAtFixedRate会立即补上延迟的任务。在网上看到几个很贴切的例子贴出来加深理解。例1:暑假来临,老师给两个学生布置了作业,schedule和scheduleAtFixedRate。老师要求学生暑假每天写2页,30天后完成作业。两个同学每天都按时完成作业,直到第10天发生意外,两个同学旅行了5天,这5天都没有做作业。任务被推迟了。这时候,两个学生采取的策略就不同了:日程表重新安排了任务时间。旅游回来的第一天,他们做的是第十一天的任务,第二天,他们做的是第十二天的任务。完成任务用了35天。scheduleAtFixedRate是一个守时的学生。她总是想按时完成老师的任务,所以在旅行回来的第一天,她就完成了前5天欠下的任务和第16天的任务,然后按照老师原来的安排,用了30天来完成作业,终于完成了任务。例2:固定费率就像今天加班到很晚,但第二天必须准时到公司。如果不小心加班到第二天早上9:00,连休息的时间都没有。固定延迟意味着你必须睡8个小时才能上班。如果加班到早上6点,下午就可以来上班了。Fixedrate强调正点,fixeddelay强调间隔。如果必须每天按时调度任务,则应采用固定速率调度,每个任务的执行时间不宜过长,以免超过周期间隔。如果任务需要每隔几分钟运行一次,请使用固定延迟调度,它实际上并不关心单个任务运行多长时间。让我们模拟一下这种情况。首先,我们修改TimerTask使某个任务消耗大量时间:TimerTasktask=newTimerTask(){privateinti=1;@Overridepublicvoidrun(){System.out.print(i+""+DateUtil.formatNow()+"开始执行,");if(i==3){ThreadUtil.sleep(11*1000);}System.out.println(DateUtil.formatNow()+"结束");我++;}};当任务执行到第三次时,会休眠11秒,会延迟后续任务。2、固定速率示例:Timertimer=newTimer("timer");timer.scheduleAtFixedRate(任务,5000,2000);设置任务延迟5秒后执行第一个任务,之后每2秒执行一次。输出:Startat:2022-10-3115:51:2412022-10-3115:51:29开始执行,2022-10-3115:51:29End22022-10-3115:51:31开始执行,2022-10-3115:51:31End32022-10-3115:51:33开始执行,2022-10-3115:51:44End*42022-10-3115:51:44开始执行,2022-10-3115:51:44结束*52022-10-3115:51:44开始执行,2022-10-3115:51:44结束*62022-10-3115:51:44开始执行,2022-10-3115:51:44结束*72022-10-3115:51:44开始执行,2022-10-3115:51:44结束*82022-10-3115:51:44开始执行,2022-10-3115:51:44结束*92022-10-3115:51:45开始执行,2022-10-3115:51:45结束102022-10-3115:51:47开始执行,2022-10-3115:51:47end112022-10-3115:51:49开始执行,2022-10-3115:51:49endifno3rd在这种情况下耗时11秒,正常的任务执行时间应该是:Startedat:2022-10-3115:51:2412022-10-3115:51:29Startexecution,2022-10-3115:51:29End22022-10-3115:51:31开始执行,2022-10-3115:51:31End32022-10-3115:51:33开始执行,2022-10-3115:51:33end*42022-10-3115:51:35开始执行,2022-10-3115:51:35end*52022-10-3115:51:37开始执行,2022-10-3115:51:37End*62022-10-3115:51:39开始执行,2022-10-3115:51:39End*72022-10-3115:51:41开始执行,2022-10-3115:51:41结束*82022-10-3115:51:43开始执行,2022-10-3115:51:43结束*92022-10-3115:51:45开始执行,2022-10-3115:51:45结束102022-10-3115:51:47开始执行,2022-10-3115:51:47结束112022-10-3115:51:49开始执行,2022-10-3115:51:49结束但是第三次??执行任务时因为执行耗时11秒,第四次应该在15:51开始:35任务已执行并完成,但直到15:51:44才完成。这11秒延迟了后续5个任务的正常执行。于是,在15:51:44,scheduleAtFixedRate赶忙将延迟的5个任务一起执行。终于赶上了原来的进度,第九个任务在15:51:45准时执行。3、固定延时示例:Timertimer=newTimer("timer");timer.schedule(task,5000,2000);output:startat:2022-10-3115:56:5912022-10-3115:57:04开始执行,2022-10-3115:57:04End22022-10-3115:57:06开始执行,2022-10-3115:57:06End32022-10-3115:57:08开始执行,2022-10-3115:57:19End*42022-10-3115:57:19开始执行,2022-10-3115:57:19End52022-10-3115:57:21开始执行,2022-10-3115:57:21End62022-10-3115:57:24开始执行,2022-10-3115:57:24End72022-10-3115:57:26开始执行,2022-10-3115:57:26End82022-10-3115:57:28开始执行,2022-10-3115:57:28End92022-10-3115:57:30开始执行,2022-10-3115:57:30end102022-10-3115:57:32开始执行,2022-10-3115:57:32如果没有第三次就结束在耗时11秒的情况下,正常的任务执行时间应该是:startat:2022-10-3115:56:5912022-10-3115:57:04开始执行,2022-10-3115:57:04结束22022-10-3115:57:06开始执行,2022-10-3115:57:06结束32022-10-3115:57:08开始执行,2022-10-3115:57:08结束*42022-10-3115:57:10开始执行,2022-10-3115:57:10结束52022-10-3115:57:12开始执行,2022-10-3115:57:12结束62022-10-3115:57:14开始执行,2022-10-3115:57:14End72022-10-3115:57:16开始执行,2022-10-3115:57:16End82022-10-3115:57:18开始执行,2022-10-3115:57:18End92022-10-3115:57:20开始执行,2022-10-3115:57:20End102022-10-3115:57:22开始执行,2022-10-3115:57:22结束使用schedule调度,第四个任务本来应该是15:57:10开始的,但是用了11秒到15:57:19而第三个任务居然用了19秒就完成了,完成后,19秒第4次立即执行,中间少了2秒的间隔。第4次完成后,每2秒开始一次,从21秒变成第5次。和我原来的猜测相反,我以为19秒完成后,第四次会在21秒每2秒执行一次,没想到19秒就立即执行了。猜测是跟delay参数有关,但是调整了delay之后还是一样,第四个task会在秒完成的时候立即执行。通过上面的测试对比,我们可以感受到Timer中fixedrate和fixeddelay的区别,但是为了避免出错,在使用Timer的时候TimerTask应该尽可能的短。4.以上其他点数仅为第三个任务加11秒。如果所有任务都需要11秒怎么办?如果每个任务执行需要11秒,那么无论是固定速率还是固定延迟,执行一个任务都是11秒。如果改为schedule(TimerTasktask,DatefirstTime,longperiod)和scheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod)调度任务,firstTime指定为10点,当前系统时间为11点,会发生什么?虽然firstTime已经过期,但是Timer会立即开始执行任务,然后按照period间隔重复执行任务。如果在TimerTask执行过程中抛出异常会怎样?Timer内部只维护一个线程。当任何一个TimerTask抛出异常时,都会导致线程终止,所有负责该Timer的任务都无法执行。4.调度多个TimerTask上一节介绍了一个可重复的TimeTask。如果执行时间大于设置的间隔周期,则会影响下一次执行TimerTask的时间点。而这一节就是单独说明一个Timer同时调度多个TimeTask也会相互影响。示例:TimerTasktask1=newTimerTask(){privateinti=1;@Overridepublicvoidrun(){System.out.print(i+"task1:"+DateUtil.formatNow()+"开始执行,");ThreadUtil.sleep(11*1000);System.out.println(DateUtil.formatNow()+"结束");我++;}};TimerTasktask2=newTimerTask(){privateinti=1;@Overridepublicvoidrun(){System.out.print(i+"task2:"+DateUtil.formatNow()+"开始执行,");ThreadUtil.sleep(11*1000);System.out.println(DateUtil.formatNow()+"结束");我++;}};计时器timer=newTimer("timer");timer.scheduleAtFixedRate(task1,5000,2000);timer.scheduleAtFixedRate(task2,5000,2000);输出:1task1:2022-10-3116:58:27开始执行,2022-10-3116:58:38结束1task2:2022-10-3116:58:38开始执行,2022-10-3116:58:49end2task2:2022-10-3116:58:49开始执行,2022-10-3116:59:00end2task1:2022-10-3116:59:00开始执行,2022-10-3116:59:11End3task1:2022-10-3116:59:11开始执行,2022-10-3116:59:22End3task2:2022-10-3116:59:22开始执行,2022-10-3116:59:33end4task2:2022-10-3116:59:33开始执行,2022-10-3116:59:44end4task1:2022-10-3116:59:44开始执行,2022-10-3116:59:55可以发现task1和task2没有按照预定的时间执行任务。根本原因是Timer内部只维护了一个线程来执行所有的TimerTasks,为了避免出错,一个Timer对象最好只调度一个TimerTask对象,除非能保证多个TimerTasks不会相互影响.因此,在编写TimerTask时应该自己捕获异常。5、取消任务Timer实际上在创建的时候内部默认维护了一个非守护线程。即使所有任务都执行完,线程也不会被销毁。Timer提供了cancel()方法,可以手动调用该方法取消定时器的所有任务并销毁定时器。如果想在Timer内部创建一个守护线程,可以使用如下构造方法创建一个定时器,并设置isDaemon为true:Timer(booleanisDaemon)Timer(Stringname,booleanisDaemon)如果没有定义name参数你自己,默认的Timer在内部自动命名为“Timer-incrementingserialnumber”,作为内部线程的线程名,在构造方法中启动这个线程。如果要取消单个任务,可以使用TimerTask的cancel()方法。当TimerTask调用cancel时,任务被取消了,但是Timer自己并不知道TimerTask被立即取消了,只是在它准备执行之前才知道,所以Timer内部仍然维护着这个任务的引用。如果想让Timer立即清除引用,可以调用Timer.purge()立即进行清除。