JUC包(java.util.concurrent)提供了对定时任务的支持,即ScheduledExecutorService接口。本文对ScheduledExecutorService的介绍将基于Timer类的使用介绍,所以请先阅读Timer类的使用介绍。定时器介绍:https://segmentfault.com/a/11...1.创建一个ScheduledExecutorService对象ScheduledExecutorServiceexecutorService=Executors.newScheduledThreadPool(5);2、ScheduledExecutorService方法ScheduledExecutorService实现了ExecutorService接口,ExecutorService接口中的方法其实属于线程池相关的一般方法,本文不做讨论。ScheduledExecutorService本身提供了以下四个方法:ScheduledFuture>schedule(Runnablecommand,longdelay,TimeUnitunit):延迟延迟单位时间后,执行一个任务ScheduledFutureschedule(Callablecallable,longdelay,TimeUnitunit):延迟delay单位时间后,执行一个taskScheduledFuture>scheduleAtFixedRate(Runnablecommand,longinitialDelay,longperiod,TimeUnitunit):延迟initialDelay单位时间后,执行一个task,然后每periodunittimeExecutiveatask(fixedrate)ScheduledFuture>scheduleWithFixedDelay(Runnablecommand,longinitialDelay,longdelay,TimeUnitunit):延迟initialDelay单位时间后,执行一个任务,然后每隔一段时间单位时间执行一个任务(固定delay)ScheduledExecutorService和Timer比较,两者提供的方法类似,区别在于Timer提供指定时间点执行任务,而ScheduledExecutorService没有。Timer提供的方法返回值都是void,而ScheduledExecutorService的方法返回值都是ScheduledFuture(继承自Future接口)。3、fixedrate和fixeddelay的区别和Timer一样。我们用一个例子来说明ScheduledExecutorService固定速率和固定延迟的区别,并与Timer进行比较。1.固定速率示例:System.out.println("Startedat:"+DateUtil.formatNow());ScheduledExecutorServiceexecutorService=Executors.newScheduledThreadPool(5);executorService.scheduleAtFixedRate(newRunnable(){inti=1;@重写publicvoidrun(){System.out.print(i+""+DateUtil.formatNow()+"开始执行,");if(i==3){ThreadUtil.sleep(11*1000);}系统.out.println(DateUtil.formatNow()+"end");i++;}},5,2,TimeUnit.SECONDS);输出:Startedat:2022-10-3117:15:4412022-10-3117:15:49开始执行,2022-10-3117:15:49end22022-10-3117:15:51开始执行,2022-10-3117:15:51end32022-10-3117:15:53开始执行,2022-10-3117:16:04end*42022-10-3117:16:04开始执行,2022-10-3117:16:04结束*52022-10-3117:16:04开始执行,2022-10-3117:16:04结束*62022-10-3117:16:04开始执行,2022-10-3117:16:04结束*72022-10-3117:16:04开始执行,2022-10-3117:16:04结束*82022-10-3117:16:04开始执行,2022-10-3117:16:04结束*92022-10-3117:16:05开始执行,2022-10-3117:16:05结束102022-10-3117:16:07开始执行,2022-10-3117:16:07结束112022-10-3117:16:09开始执行,2022-10-3117:16:09结束,没有11秒的耗时,正常输出应该是:startat:2022-10-3117:15:4412022-10-3117:15:49开始执行,2022-10-3117:15:49结束22022-10-3117:15:51开始执行,2022-10-3117:15:51结束32022-10-3117:15:53开始执行,2022-10-3117:15:53结束42022-10-3117:15:55开始执行,2022-10-3117:15:55end52022-10-3117:15:57开始执行,2022-10-3117:15:57end62022-10-3117:15:59开始执行,2022-10-3117:15:59end72022-10-3117:16:01开始执行,2022-10-3117:16:01end82022-10-3117:16:03开始??执行,2022-10-3117:16:03end92022-10-3117:16:05开始执行,2022-10-3117:16:05end102022-10-3117:16:07开始执行,2022-10-3117:16:07end112022-10-3117:16:09开始执行,2022-10-3117:16:09end从测试结果可以看出,当一个任务执行时间过长,超过设定的周期时时间单位,会影响后续5个任务的准时执行。当耗时任务完成后,ScheduledExecutorService会立即补上延迟的5个任务,保证后续任务按预期按时执行。间隔执行与ScheduledExecutorService和Timer的固定速率完全一样。读者可以直接参考Timer固定速率的介绍。2、固定延迟示例:System.out.println("Startedat:"+DateUtil.formatNow());ScheduledExecutorServiceexecutorService=Executors.newScheduledThreadPool(5);executorService.scheduleWithFixedDelay(newRunnable(){inti=1;@Overridepublicvoidrun(){System.out.print(i+""+DateUtil.formatNow()+"开始执行,");if(i==3){ThreadUtil.sleep(11*1000);}System.out.println(DateUtil.formatNow()+"end");i++;}},5,2,TimeUnit.SECONDS);输出:12022-10-3117:16:41开始执行,2022-10-3117:16:41结束22022-10-3117:16:43开始执行,2022-10-3117:16:43End32022-10-3117:16:45开始执行,2022-10-3117:16:56end*42022-10-3117:16:58开始执行,2022-10-3117:16:58end52022-10-3117:17:00开始执行,2022-10-3117:17:00End62022-10-3117:17:02开始执行,2022-10-3117:17:02End72022-10-3117:17:04开始执行,2022-10-3117:17:04End82022-10-3117:17:06开始执行,2022-10-3117:17:06End92022-10-3117:17:08开始执行,2022-10-3117:17:08当end不用11秒时,正常输出应该是:12022-10-3117:16:41开始执行,2022-10-3117:16:41End22022-10-3117:16:43开始执行,2022-10-3117:16:43结束32022-10-3117:16:45开始执行,2022-10-3117:16:45结束42022-10-3117:16:47开始执行,2022-10-3117:16:47结束52022-10-3117:16:49开始执行,2022-10-3117:16:49结束62022-10-3117:16:51开始执行,2022-10-3117:16:51结束72022-10-3117:16:53开始执行,2022-10-3117:16:53结束82022-10-3117:16:55开始执行,2022-10-3117:16:55结束92022-10-3117:16:57开始执行,2022-10-3117:16:57结束固定延迟是什么时候任务执行时间过长,超过设定的延迟时间单位,后续任务将被推迟。这个设计和Timer是一样的,但是和Timer有一点点区别。在Timer类的使用介绍中,提到了Timer类的固定延时,和我的想象不符。Timer会在执行完第三个任务后立即执行第四个任务,然后每隔2秒执行第五个任务。ScheduledExecutorService完全符合我的想象。当第三个任务执行完后,它会每隔2秒执行第四个任务。所以在固定延时下,Timer和ScheduledExecutorService的实现有点不同。4.调度多个任务在Timer中,一个TimerTask对象就是一个任务。在ScheduledExecutorService中,一个Runnable对象有一个任务。第三部分描述了固定速率和固定延迟如何影响可重复任务(一个Runnable对象)的多次执行。本节介绍ScheduledExecutorService如何同时调度多个可重复任务。与只有一个线程的Timer不同,ScheduledExecutorService内部使用了一个线程池,支持自己设置线程数。所以理论上,如果要添加2个任务,ScheduledExecutorService设置线程数为2,不会相互影响。让我们验证一下。定义一个任务,第三次执行时会休眠11秒:classTaskimplementsRunnable{privateinti=1;私有字符串名称;公共任务(字符串名称){this.name=name;}@Overridepublicvoidrun(){System.out.println(i+""+name+":"+DateUtil.formatNow()+"开始执行");if(i==3){ThreadUtil.sleep(11*1000);}System.out.println(i+""+name+":"+DateUtil.formatNow()+"执行结束");我++;}}使用ScheduledExecutorService进行调度:System.out.println("Startat:"+DateUtil.formatNow());ScheduledExecutorServiceexecutorService=Executors.newScheduledThreadPool(2);Tasktask1=newTask("task1");Tasktask2=新任务(“task2”);executorService.scheduleWithFixedDelay(task1、5、2、TimeUnit.SECONDS);executorService.scheduleWithFixedDelay(task2、5、2、TimeUnit.SECONDS);由于task1和task2的日志在控制台输出的时候会混在一起,不太好读,所以我这里把task1和task2的日志分开了。任务1日志:开始于:2022-10-3117:49:511任务1:2022-10-3117:49:56开始执行1任务1:2022-10-3117:49:56执行结束2任务1:2022-10-3117:49:58开始执行2task1:2022-10-3117:49:58执行结束3task1:2022-10-3117:50:00开始执行3task1:2022-10-3117:50:11执行结束4任务1:2022-10-3117:50:13执行开始4任务1:2022-10-3117:50:13执行结束5任务1:2022-10-3117:50:15执行开始5task1:2022-10-3117:50:15执行结束task2log:Startedat:2022-10-3117:49:511task2:2022-10-3117:49:56Startexecution1task2:2022-10-3117:49:56执行结束2task2:2022-10-3117:49:58开始执行2task2:2022-10-3117:49:58结束执行3task2:2022-10-3117:50:00开始执行3task2:2022-10-3117:50:11执行结束4task2:2022-10-3117:50:13开始执行4task2:2022-10-3117:50:13executionend5task2:2022-10-3117:50:15开始执行经过测试可以确认,当添加的任务数不超过线程池的线程数时,即使任务是时间-consuming,它们不会互相影响,只会影响各自任务下次执行的时间点。如果添加的任务数超过线程数怎么办?我们来测试添加3个任务,线程数还是2(task1,5,2,TimeUnit.SECONDS);executorService.scheduleWithFixedDelay(task2,5,2,TimeUnit.SECONDS);executorService.scheduleWithFixedDelay(task3,5,2,TimeUnit.SECONDS);分别显示三个任务的日志。task1:startat:2022-10-3117:53:221task1:2022-10-3117:53:27开始执行1task1:2022-10-3117:53:27执行结束2task1:2022-10-3117:53:29开始执行2task1:2022-10-3117:53:29执行结束3task1:2022-10-3117:53:31开始执行3task1:2022-10-3117:53:42执行结束4task1:2022-10-3117:53:44开始执行4task1:2022-10-3117:53:44结束执行5task1:2022-10-3117:53:46开始执行5task1:2022-10-3117:53:46执行结束6task1:2022-10-3117:53:48开始执行6task1:2022-10-3117:53:48执行结束7task1:2022-10-3117:53:50开始执行7task1:2022-10-3117:53:50执行结束8task1:2022-10-3117:53:52开始执行8task1:2022-10-3117:53:529task1:2022-10-31执行结束17:53:549task1:2022-10-31开始执行17:53:5410task1:2022-执行结束10-3117:53:56开始执行10任务1:2022-10-3117:53:56执行结束任务2:开始于:2022-10-3117:53:221任务2:2022-10-3117:53:27开始执行1task2:2022-10-3117:53:27结束执行2task2:2022-10-3117:53:29开始执行2task2:2022-10-3117:53:29执行结束3task2:2022-10-3117:53:31开始执行3task2:2022-10-3117:53:42执行结束4task2:2022-10-3117:53:44开始执行4task2:2022-10-3117:53:44执行结束5task2:2022-10-3117:53:46开始5task2:2022-10-3117:53:46完成6task2:2022-10-3117:53:48开始6task2:2022-10-3117:53:48执行结束7task2:2022-10-3117:53:50执行开始7task2:2022-10-3117:53:50执行结束8task2:2022-10-3117:53:52开始执行8task2:2022-10-3117:53:52执行结束9task2:2022-10-3117:53:54开始执行9task2:2022-10-3117:53:54执行结束10task2:2022-10-3117:53:56开始执行10task2:2022-10-3117:53:56执行结束task3:开始于:2022-10-3117:53:221task3:2022-10-3117:53:27开始执行1task3:2022-10-3117:53:27执行结束2task3:2022-10-3117:53:29开始执行2task3:2022-10-3117:53:29执行结束3task3:2022-10-3117:53:42开始执行3task3:2022-10-3117:53:53执行结束4task3:2022-10-3117:53:55开始执行4task3:2022-10-3117:53:55Executionended5task3:2022-10-3117:53:57Executionstarted5task3:2022-10-3117:53:57Executionended从上面的日志可以看出,task1和task2执行正常,但是从第三次执行task3就出错了。task3第三次的正确时间应该是17:53:31,但实际上延迟到了17:53:42。从这点我们可以推断,当时两个线程都在执行task1和task2的第三个任务,耗时11秒,导致task3被延迟。因此,我们在使用ScheduledExecutorService调度多个任务时,要注意尽量缩短任务的处理时间,避免任务数超过线程数。5、其他关键任务执行过程中抛出异常怎么办?在Timer内部,单个线程处理所有任务。当抛出异常时,Timer线程将停止运行;ScheduledExecutorService内部是一个线程池。当抛出异常时,任务所在的线程会停止运行并被回收,后面的任务就无法执行了。触发执行,其他线程不会受到影响,所以在写任务执行代码的时候要注意捕获异常。