前面介绍了普通线程池的用法,就大多数任务而言,它们对具体的执行时机并没有特殊要求,最可能是但愿早点跑完早点出结果。不过对于须要定时执行的任务来讲,它们要求在特定的时间点运行,而且每每不止运行一次,还要周期性地反复运行。因为普通线程池知足不了此类定时运行的需求,所以Java又提供了定时器线程池来实现定时与周期执行任务的功能。
普通线程池的工具类名叫ExecutorService,定时器线程池的工具类则叫作ScheduledExecutorService,添加了Scheduled前缀,表示它是一种有计划的、预先安排好的线程池。有别于划分了四大类的普通线程池,定时器线程池仅仅分红了两类:单线程的定时器线程池和固定数量的定时器线程池。其中单线程的定时器线程池经过newSingleThreadScheduledExecutor方法得到,它的建立代码示例以下:html
// 建立一个延迟一次的单线程定时器 ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newSingleThreadScheduledExecutor();
至于固定数量的定时器线程池则经过newScheduledThreadPool方法得到,它的建立代码示例以下:多线程
// 建立一个延迟一次的多线程定时器(线程池大小为3) ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3);
虽然定时器线程池只有两类,但定时器的调度方式有三种之多,主要是依据启动次数与周期长度来划分,详细说明以下:
一、定时任务只启动一次。
此时调用线程池对象的schedule方法,该方法的第一个参数为任务实例,第二个和第三个参数分别是延迟执行的时长及其单位。
二、每间隔若干时间周期启动定时任务。
此时调用线程池对象的scheduleAtFixedRate方法,该方法的第一个参数为任务实例,第二个参数为首次执行的延迟时长,第三个参数分别为后续运行的间隔时长,第四个参数则为时长单位。
三、固定延迟若干时间启动定时任务。
此时调用线程池对象的scheduleWithFixedDelay方法,该方法的参数说明基本同scheduleAtFixedRate方法。两个方法的区别在于:前者的间隔时间从上个任务的开始时间起计算,后者的间隔时间从上个任务的结束时间起计算。
除了以上的三个调度方法,ScheduledExecutorService还拥有ExecutorService的所有方法,包括getPoolSize、getActiveCount、shutdown等等,由于它原本就是从ExecutorService派生而来的呀。ide
下面作个实验观察一下两种定时器线程池的运行过程,实验开始前先定义一个参观任务,主要用来打印当前的操做日志,包括操做时间、操做线程、操做描述等信息。参观任务的代码例子以下所示:工具
// 定义一个参观任务 private static class Visit implements Runnable { private String name; // 任务名称 private int index; // 任务序号 public Visit(String name, int index) { this.name = name; this.index = index; } @Override public void run() { // 如下打印操做日志,包括操做时间、操做线程、操做描述等信息 String desc = String.format("%s的第%d个任务到此一游", name, index); PrintUtils.print(Thread.currentThread().getName(), desc); } };
而后命令单线程的定时器线程池调用schedule方法执行一次的定时任务,具体的实验代码示例以下:测试
// 测试延迟一次的单线程定时器 private static void testSingleScheduleOnce() { // 建立一个延迟一次的单线程定时器 ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newSingleThreadScheduledExecutor(); for (int i=0; i<5; i++) { // 循环开展5个调度 // 建立一个参观任务 Visit visit = new Visit("延迟一次的单线程定时器", i); // 命令线程池开展任务调度。延迟1秒后执行参观任务 pool.schedule(visit, 1, TimeUnit.SECONDS); } }
运行以上的实验代码,观察到以下的线程池日志:this
15:49:16.122 pool-1-thread-1 延迟一次的单线程定时器的第0个任务到此一游 15:49:16.123 pool-1-thread-1 延迟一次的单线程定时器的第1个任务到此一游 15:49:16.123 pool-1-thread-1 延迟一次的单线程定时器的第2个任务到此一游 15:49:16.124 pool-1-thread-1 延迟一次的单线程定时器的第3个任务到此一游 15:49:16.124 pool-1-thread-1 延迟一次的单线程定时器的第4个任务到此一游
由日志可见,该定时器线程池自始至终只有惟一一个的线程在运行。线程
再来测试固定数量的定时器线程池,此时换成调用scheduleAtFixedRate方法,准备以固定频率周期性地执行定时任务,具体的实验代码示例以下:日志
// 测试固定速率的多线程定时器 private static void testMultiScheduleRate() { // 建立一个固定速率的多线程定时器(线程池大小为3) ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3); for (int i=0; i<5; i++) { // 循环开展5个调度 // 建立一个参观任务 Visit visit = new Visit("固定速率的多线程定时器", i); // 命令线程池开展任务调度。第一次延迟1秒后执行参观任务,之后每间隔3秒执行下一个参观任务 pool.scheduleAtFixedRate(visit, 1, 3, TimeUnit.SECONDS); } }
运行以上的实验代码,观察到以下的线程池日志:orm
15:50:21.859 pool-1-thread-1 固定速率的多线程定时器的第0个任务到此一游 15:50:21.859 pool-1-thread-2 固定速率的多线程定时器的第1个任务到此一游 15:50:21.859 pool-1-thread-3 固定速率的多线程定时器的第2个任务到此一游 15:50:21.860 pool-1-thread-3 固定速率的多线程定时器的第3个任务到此一游 15:50:21.861 pool-1-thread-3 固定速率的多线程定时器的第4个任务到此一游 15:50:24.790 pool-1-thread-3 固定速率的多线程定时器的第1个任务到此一游 15:50:24.791 pool-1-thread-3 固定速率的多线程定时器的第3个任务到此一游 15:50:24.792 pool-1-thread-3 固定速率的多线程定时器的第4个任务到此一游 15:50:24.793 pool-1-thread-2 固定速率的多线程定时器的第2个任务到此一游 15:50:24.798 pool-1-thread-1 固定速率的多线程定时器的第0个任务到此一游
由日志可见,该定时器线程池一共开启了三个线程来执行定时任务,注意到每一个任务的先后日志间隔时间不足3秒,正好说明间隔的3秒并不是先后两次运行的首尾间隔。htm
那么调用方法改为scheduleWithFixedDelay,试试以固定间隔周期性地执行定时任务会是什么样的,具体的实验代码示例以下:
// 测试固定延迟的多线程定时器 private static void testMultiScheduleDelay() { // 建立一个固定速率的多线程定时器(线程池大小为3) ScheduledExecutorService pool = (ScheduledExecutorService) Executors.newScheduledThreadPool(3); for (int i=0; i<5; i++) { // 循环开展5个调度 // 建立一个参观任务 Visit visit = new Visit("固定延迟的多线程定时器", i); // 命令线程池开展任务调度。第一次延迟1秒后执行参观任务,之后每3秒执行下一个参观任务 pool.scheduleWithFixedDelay(visit, 1, 3, TimeUnit.SECONDS); } }
运行以上的实验代码,观察到以下的线程池日志:
16:10:19.281 pool-1-thread-1 固定延迟的多线程定时器的第0个任务到此一游 16:10:19.281 pool-1-thread-2 固定延迟的多线程定时器的第1个任务到此一游 16:10:19.281 pool-1-thread-3 固定延迟的多线程定时器的第2个任务到此一游 16:10:19.283 pool-1-thread-3 固定延迟的多线程定时器的第3个任务到此一游 16:10:19.283 pool-1-thread-2 固定延迟的多线程定时器的第4个任务到此一游 16:10:22.283 pool-1-thread-1 固定延迟的多线程定时器的第1个任务到此一游 16:10:22.284 pool-1-thread-2 固定延迟的多线程定时器的第3个任务到此一游 16:10:22.285 pool-1-thread-3 固定延迟的多线程定时器的第2个任务到此一游 16:10:22.286 pool-1-thread-3 固定延迟的多线程定时器的第4个任务到此一游 16:10:22.287 pool-1-thread-1 固定延迟的多线程定时器的第0个任务到此一游
由日志可见,此时每一个任务的先后日志时间均不小于3秒,证实了scheduleWithFixedDelay方法的确采起了固定间隔而非固定速率。
更多Java技术文章参见《Java开发笔记(序)章节目录》