大纲 概述 Java 中 ScheduledExecutorService 是基于线程池设计的定时任务接口,其中的一个实现类是 ScheduledThreadPoolExecutor。每个调度任务都会分配到线程池中的一个线程去执行,也就是说任务是并发执行的,互不影响。
ScheduledThreadPoolExecutor 继承了 ThreadPoolExecutor,可以通过线程池进行任务的管理和调度。 ScheduledThreadPoolExecutor 实现 ScheduledExecutorService 接口,实现了一些定时任务处理的方法。 提示
ScheduledThreadPoolExecutor 有两种创建方式(如下所示),且它们的作用是等价的。
1 ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1 );
1 ScheduledExecutorService scheduledExecutorService1 = Executors.newScheduledThreadPool(1 );
schedule () 方法 schedule()
方法的作用是,提交一个延迟执行的任务,任务从提交时间算起延迟单位 unit
的 deplay
时间后执行,提交的不是周期任务,任务只会执行一次。
1 2 public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);public <V> ScheduledFuture<V> schedule (Callable<V> callable, long delay, TimeUnit unit) ;
参数说明:period
:间隔时间unit
:参数是时间单位command
:任务实例可以实现 Runnable 接口 可以实现 Callable 接口,然后通过 ScheduledFuture
获取任务的执行结果 使用案例一 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class SchudledExecutorTest { public static void main (String[] args) { System.out.println("当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1 ); executorService.schedule(() -> { System.out.println("我是使用schedule()方法执行" ); Thread thread = Thread.currentThread(); System.out.println("我是间隔1s执行的任务,线程名称为:" + thread.getName() + ",线程ID为:" + thread.getId() + ",当前时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); }, 1 , TimeUnit.SECONDS); shutdown(executorService); } public static void shutdown (ExecutorService executorService) { try { TimeUnit.SECONDS.sleep(60 ); } catch (Exception e) { e.printStackTrace(); } if (!executorService.isShutdown()) { executorService.shutdown(); } } }
程序运行的输出结果为:
1 2 3 当前时间为:2019-03-24 21:01:00 我是使用schedule()方法执行 我是间隔1s执行的任务,线程名称为:pool-1-thread-1,线程ID为:27,当前时间:2019-03-24 21:01:01
使用案例二 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class SchudledExecutorTest { public static void main (String[] args) { System.out.println("当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1 ); ScheduledFuture<Integer> future = executorService.schedule(new Callable<Integer>() { @Override public Integer call () throws Exception { System.out.println("我是使用schedule()方法执行" ); Thread thread = Thread.currentThread(); System.out.println("我是间隔1s执行的任务,线程名称为:" + thread.getName() + ",线程ID为:" + thread.getId() + ",当前时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); return Integer.MAX_VALUE; } }, 1 , TimeUnit.SECONDS); try { Integer result = future.get(); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } shutdown(executorService); } public static void shutdown (ExecutorService executorService) { try { TimeUnit.SECONDS.sleep(60 ); } catch (Exception e) { e.printStackTrace(); } if (!executorService.isShutdown()) { executorService.shutdown(); } } }
程序运行的输出结果为:
1 2 3 4 当前时间为:2019-03-24 21:03:11 我是使用schedule()方法执行 我是间隔1s执行的任务,线程名称为:pool-1-thread-1,线程ID为:27,当前时间:2019-03-24 21:03:12 2147483647
scheduleAtFixedRate () 方法 scheduleAtFixedRate ()
方法的作用是,相对起始时间点以固定频率调用指定的任务(Fixed-Rate 任务)。当把任务提交到线程池并延迟 initialDelay
时间后开始执行任务,然后从 initialDelay + period
时间点再次执行,接着在 initialDelay + 2 * period
时间点再次执行,循环往复,直到任务在执行过程中抛出了异常,或者调用了任务的 cancel()
方法取消了任务,或者关闭了线程池。
1 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
参数说明:command
:任务实例,实现 Runnable 接口initialDelay
:初始延迟时间period
:间隔时间unit
:时间单位 使用案例一 这里将模拟任务执行的时间(总耗时)小于延迟间隔时间 period
的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class SchudledExecutorTest { public static void main (String[] args) { System.out.println("当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1 ); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run () { System.out.println( "开始测试,当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println( "结束测试,当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); } }, 5 , 5 , TimeUnit.SECONDS); shutdown(scheduledExecutorService); } public static void shutdown (ExecutorService executorService) { try { TimeUnit.SECONDS.sleep(60 ); } catch (Exception e) { e.printStackTrace(); } if (!executorService.isShutdown()) { executorService.shutdown(); } } }
程序运行的输出结果为:
1 2 3 4 5 6 7 当前时间为:2019-03-24 21:14:14 开始测试,当前时间为:2019-03-24 21:14:19 结束测试,当前时间为:2019-03-24 21:14:22 开始测试,当前时间为:2019-03-24 21:14:24 结束测试,当前时间为:2019-03-24 21:14:27 开始测试,当前时间为:2019-03-24 21:14:29 结束测试,当前时间为:2019-03-24 21:14:32
使用案例二 这里将模拟任务执行的时间(总耗时)大于延迟间隔时间 period
的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class SchudledExecutorTest { public static void main (String[] args) { System.out.println("当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1 ); scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run () { System.out.println( "开始测试,当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); try { TimeUnit.SECONDS.sleep(6 ); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println( "结束测试,当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); } }, 5 , 5 , TimeUnit.SECONDS); shutdown(scheduledExecutorService); } public static void shutdown (ExecutorService executorService) { try { TimeUnit.SECONDS.sleep(60 ); } catch (Exception e) { e.printStackTrace(); } if (!executorService.isShutdown()) { executorService.shutdown(); } } }
程序运行的输出结果为:
1 2 3 4 5 6 7 当前时间为:2019-03-24 21:12:32 开始测试,当前时间为:2019-03-24 21:12:37 结束测试,当前时间为:2019-03-24 21:12:43 开始测试,当前时间为:2019-03-24 21:12:43 结束测试,当前时间为:2019-03-24 21:12:49 开始测试,当前时间为:2019-03-24 21:12:49 结束测试,当前时间为:2019-03-24 21:12:55
总结
在调用 scheduleAtFixedRate()
方法时,任务以固定频率执行,这里分为两种情况:
如果任务执行的时间(耗时)小于 period
延迟间隔时间,那么任务的执行周期为:initialDelay
、initialDelay + period
、initialDelay + 2 * period
、….。 如果任务执行的时间(耗时)大于 period
延迟间隔时间,那么当任务执行完后,下一次任务将立即执行。也就是说下一次任务不会再按照预期的时间间隔执行,最终任务的执行周期为:initialDelay
、initialDelay + taskExecuteTime
、initialDelay + 2 * taskExecuteTime
、….。 scheduleWithFixedDelay () 方法 scheduleWithFixedDelay()
方法的作用是,当任务执行完毕后,让其延迟固定时间后再次运行(Fixed-Delay 任务)。其中参数 initialDelay
表示提交任务后延迟多少时间开始执行任务,delay
表示当任务执行完毕后延长多少时间后再次运行任务,unit
是 initialDelay
和 delay
的时间单位。任务会一直重复执行,直到任务在执行过程中抛出了异常,或者调用了任务的 cancel()
方法取消了任务,或者关闭了线程池。
1 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
参数说明:command
:任务实例,实现 Runnable 接口initialDelay
:初始延迟时间delay
:延迟间隔时间unit
:时间单位 案例一 这里将模拟任务执行的时间(总耗时)小于延迟间隔时间 delay
的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class SchudledExecutorTest { public static void main (String[] args) { System.out.println("当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1 ); scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { @Override public void run () { System.out.println( "开始测试,当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); try { TimeUnit.SECONDS.sleep(3 ); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println( "结束测试,当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); } }, 5 , 5 , TimeUnit.SECONDS); shutdown(scheduledExecutorService); } public static void shutdown (ExecutorService executorService) { try { TimeUnit.SECONDS.sleep(60 ); } catch (Exception e) { e.printStackTrace(); } if (!executorService.isShutdown()) { executorService.shutdown(); } } }
程序运行的输出结果为:
1 2 3 4 5 6 7 当前时间为:2019-03-24 21:47:15 开始测试,当前时间为:2019-03-24 21:47:20 结束测试,当前时间为:2019-03-24 21:47:23 开始测试,当前时间为:2019-03-24 21:47:28 结束测试,当前时间为:2019-03-24 21:47:31 开始测试,当前时间为:2019-03-24 21:47:36 结束测试,当前时间为:2019-03-24 21:47:39
案例一 这里将模拟任务执行的时间(总耗时)大于延迟间隔时间 delay
的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class SchudledExecutorTest { public static void main (String[] args) { System.out.println("当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1 ); scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { @Override public void run () { System.out.println( "开始测试,当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); try { TimeUnit.SECONDS.sleep(6 ); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println( "结束测试,当前时间为:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ))); } }, 5 , 5 , TimeUnit.SECONDS); shutdown(scheduledExecutorService); } public static void shutdown (ExecutorService executorService) { try { TimeUnit.SECONDS.sleep(60 ); } catch (Exception e) { e.printStackTrace(); } if (!executorService.isShutdown()) { executorService.shutdown(); } } }
程序运行的输出结果为:
1 2 3 4 5 6 7 当前时间为:2019-03-24 21:51:31 开始测试,当前时间为:2019-03-24 21:51:36 结束测试,当前时间为:2019-03-24 21:51:42 开始测试,当前时间为:2019-03-24 21:51:47 结束测试,当前时间为:2019-03-24 21:51:53 开始测试,当前时间为:2019-03-24 21:51:58 结束测试,当前时间为:2019-03-24 21:52:04
总结
在调用 scheduleWithFixedDelay()
方法时,不管任务执行的时间(耗时)是多久,下一次任务的执行时间都是上一次任务执行完后再等待延迟间隔 delay
时间后才执行。简而言之,每次执行时间为上一次任务结束起向后推一个时间间隔,即任务的执行周期为:initialDelay
、initialDelay + taskExecuteTime + delay
、initialDelay + 2 * taskExecuteTime + 2 * delay
、…。
参考资料