Timer和TimerTask
基本用法
TimerTask表示一个定时任务,它是一个抽象类,实现了Runnable,具体的定时任务需要继承该类,实现run方法。
Timer是一个具体类,它负责定时任务的调度和执行,主要方法有:
// 在指定绝对时间time运行任务task
public void schedule(TimerTask task, Date time);
// 在当前时间延时delay毫秒后运行任务task
public void schedule(TimerTask task, long delay);
// 固定延时重复执行,第一次计划执行时间为firstTime,如果firstTime小于当前时间,则立即执行
// 后一次的计划执行时间为前一次“实际执行”时间加上period
public void schedule(TimerTask task, Date firstTime, long period);
// 固定延时重复执行,第一次计划执行时间为当前时间加上delay毫秒
public void schedule(TimerTask task, long delay, long period);
// 固定频率重复执行。第一次计划执行时间为firstTime,如果firstTime小于当前时间,则立即执行
// 后一次的计划执行时间为前一次“计划执行”时间加上period
public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period);
// 固定频率重复执行。第一次计划执行时间为当前时间加上delay毫秒
public void scheduleAtFixedRate(TimerTask task, long delay, long period);
固定延时和固定频率的区别:二者都是重复执行,但后一次任务执行相对的时间是不一样的。 固定延时它是基于上一次任务的“实际”执行时间来算的,如果由于某种原因上一次执行时间延迟了,那么这次任务也延时。
基本示例
public class TimerFixedDelay {
static class LongRunningTask extends TimerTask {
@Override
public void run() {
try {
System.out.println("start sleep");
Thread.sleep(5000);
System.out.println("sleep over");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class FixedDelayTask extends TimerTask {
@Override
public void run() {
System.out.println("FixedDelayTask:" + System.currentTimeMillis());
}
}
static class FixedRateTask extends TimerTask {
@Override
public void run() {
System.out.println("FixedRateTask:" + System.currentTimeMillis());
}
}
public static void main(String[] args) {
Timer timer = new Timer();
// 延时10毫秒后执行,只执行一次,但耗时5秒
timer.schedule(new LongRunningTask(), 10);
// 延时100毫秒后执行,相对于上次实际执行时间,每隔2秒执行一次
timer.schedule(new FixedDelayTask(), 100, 2000);
// 延时100毫秒后执行,相对于上次计划执行时间,每隔1秒执行一次,
timer.scheduleAtFixedRate(new FixedRateTask(), 100, 1000);
}
}
通过这个示例发现第二和第三个任务只有在第一个任务运行结束后才会开始运行。
同时可以看出固定延时和固定频率的区别,第二个任务是固定延时,它是在第一个任务运行结束后才开始运行,2秒一次。第三个任务是固定频率,在第一个任务运行结束后才运行,但它会把之前没有运行的次数补过来,一下运行了5次。
基本原理
Timer内部主要由任务队列和Timer线程两部分组成。
任务队列是一个基于堆实现的优先级队列,按照下次执行的时间排优先级
Timer线程负责执行所有的定时任务,一个Timer对象只有一个Timer线程,所以任务会被延迟。
对于固定延时的任务,延时相对的是任务执行前的当前时间,而不是任务执行后。
对于固定频率的任务,延时相对的是最先的计划
死循环
一个Timer对象只有一个Timer线程,这意味着:定时任务不能耗时太长,更不能无限循环。
异常处理
在执行任何一个任务的run方法时,一旦run抛出异常,Timer线程就会退出,从而所有定时任务都会被取消
小结
- 后台只有一个线程在运行
- 固定频率的任务被延迟后,可能会立即执行多次,将次数不够
- 固定延时任务的延时相对的是任务执行前的时间
- 不要在定时任务中使用无限循环,会导致后面的任务无法执行
- 一个定时任务的未处理异常会导致所有定时任务被取消
- Timer 对提交的任务调度是基于绝对时间而不是相对时间的,所以通过其提交的任务对系统时钟的改变是敏感的(譬如提交延迟任务后修改了系统时间会影响其执行)
ScheduledExecutorService
基本用法
ScheduledExecutorService 是一个接口
public interface ScheduledExecutorService extends ExecutorService {
// 单次执行,在指定延时delay后运行command
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
// 单次执行,在指定延时delay后运行callable
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
// 固定频率重复执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
// 固定延时重复执行
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
返回类型ScheduledFuture,它是一个接口,扩展了Future和Delayed。
对于固定延时的任务,它是从任务执行后开始算的,第一次执行为当前时间 + initialDelay,第二次为第一次任务执行结束后再加上delay
对于固定频率的任务,第一次执行时间为当前时间 + initialDelay,第二次为当前时间 + initialDelay + period
ScheduledExecutorService的主要实现类是ScheduledTheradPoolExecutor,它是线程池ThreadPoolExecutor的子类。它的任务队列是一个无界的优先级队列,所以最大线程数对它没有作用。
工厂类Executors提供了一些创建ScheduledTheradPoolExecutor的方法
// 单线程的定时任务执行服务
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
// 多线程的定时任务执行服务
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
小结
- 背后是线程池,可以有多个线程执行任务
- 在任务执行后再设置下次执行时间,对于固定延时的任务更合理
- 任务执行的线程会普通任务执行过程中的所有异常,一个定时任务的异常不会影响其他定时任务,不过,发生异常的任务(即使是一个重复任务)不会再被调度