任务调度
定义:任务调度是基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。
Timer
Timer 是jdk提供的一个定时器工具,会在主线程之外起一个单独线程执行指定的计划任务。
public class TimerTest extends TimerTask {
private String jobName;
public TimerTest(String jobName) {
this.jobName = jobName;
}
@Override
public void run() {
System.out.println("执行:" + jobName);
}
public static void main(String[] args) {
Timer timer = new Timer();
long delay1 = 1 * 1000;
long interval1 = 1000;
//从现在开始1秒后每隔1秒执行一次job1
timer.schedule(new TimerTest("job1"), delay1, interval1);
long delay2 = 2 * 1000;
long interval2 = 2000;
// 从现在开始2秒钟之后,每隔2秒钟执行一次 job2
timer.schedule(new TimerTest("job2"), delay2, interval2);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终止执行
timer.cancel();
}
}
Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务
ScheduledExecutor
ScheduledExecutor 类则在自定义线程池的基础上增加了周期性执行任务的功能
ScheduledThreadPoolExecutor 线程池中的每一个线程负责执行一个定时任务,相互之间互不干扰,并发执行,线程池会轮询任务状态,执行时间到来才会真正执行线程。
public class ScheduledExecutorTest implements Runnable {
private String jobName;
public ScheduledExecutorTest(String jobName) {
this.jobName = jobName;
}
public void run() {
System.out.println("Start: " + getDate());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("execute " + jobName);
System.out.println("End: " + getDate());
}
//获取当前时间
public String getDate() {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
return df.format(new Date());// new Date()为获取当前系统时间
}
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
long initialDelay1 = 1;
long period1 = 2;
//固定时间间隔
service.scheduleAtFixedRate(new ScheduledExecutorTest("job1"),
initialDelay1, period1, TimeUnit.SECONDS);
long initialDelay2 = 1;
long peroid2 = 3;
//本次执行结束和下次开始执行时间保持在三秒
//每次执行时间:executeTime + peroid executeTime可能会变化,但是peroid不变
/*service.scheduleWithFixedDelay(new ScheduledExecutorTest("job2"),
initialDelay2, peroid2, TimeUnit.SECONDS); */
}
}
使用Executors.newScheduledThreadPool(10)创建一个核心线程数量为10的线程池。
方法区别:
1.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
参数:
- Runnable 需要执行的task
- initialDelay 延迟至启动任务的时间
- period 本次开始到下次开始的时间间隔
注:
- 若本次job执行时间小于period,本次开始执行和下次开始之间时间间隔固定
- 若本次job执行时间大于period,则本次开始时间和下次开始时间间隔为job执行时间
- 每次执行时间 :executeTime < period ? peroid : executeTime
- 不会因为上一个线程的调度失效延迟而受到影响,固定频率触发执行
2.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)
注:
- 本次执行结束和下次开始执行时间保持在三秒
- 每次执行时间:executeTime + peroid executeTime可能会变化,但是peroid不变
- 受到上一个线程调度影响,可能会推迟本次任务调度的执行,固定延时触发执行
实现原理:
ScheduledThreadPool创建的线程池底层使用的是DelayQueue
Delayed元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。
当一个元素的 getDelay(TimeUnit.NANOSECONDS)方法返回一个小于等于0的值时,将发生到期。即使无法使用take或poll移除未到期的元素,也不会将这些元素作为正常元素对待。例如,size方法同时返回到期和未到期元素的计数。此队列不允许使用null元素
ScheduledThreadPoolExecutor是把任务添加到DelayedWorkQueue中,而DelayedWorkQueue则是类似于DelayQueue,内部维护着一个以时间为先后顺序的优先级队列,使用compareTo()方法使用与DelayedWorkQueue队列对其元素ScheduledThreadPoolExecutor task进行排序
小结:
上面创建线程池建议使用ScheduledThreadPoolExecutor,可以更加精确控制。
构造函数其中一个参数为DelayedWorkQueue,内部通过一个RunnableScheduledFuture数组保存Runnable任务,默认初始长度为16,它是一个优先级队列
它根据这个任务的下次执行时间来比较大小,这样能够保证queue[0]位置上的元素是最近需要执行的任务。