ScheduledExecutorService是什么?
ScheduledExecutorService是Java线程池中重要的几个接口之一。它除了支持原生线程池的功能外,同时支持定时任务处理的能力。在很多优秀的框架、中间件中都有被使用。
在JDK中,为ScheduledExecutorService提供了一个默认的实现类:ScheduledThreadPoolExecutor
,类图:

ScheduledExecutorService的使用

ScheduledExecutorService 包含三个方法:
schedule()
:返回一个ScheduledFuture接口的具体实现。
ScheduledFuture是一个延迟的、结果可接受的操作,可将其取消。通常已安排的 future 是用 ScheduledExecutorService 安排任务的结果。
ScheduledFuture只是在Future基础上还集成了Comparable和Delayed的接口。使其具有延迟、排序、获得异步计算结果的特性。
它用于表示ScheduledExecutorService中提交了任务的返回结果。我们通过Delayed的接口getDelay()方法知道该任务还有多久才会被执行。
JDK中并没提供ScheduledFuture的实现类。只有在ScheduledExecutorService中提交了任务,才能返回一个实现了ScheduledFuture接口的对象。
scheduleAtFixedRate()
:此方法用于周期性执行任务; 当任务耗时长于周期,那么下一个周期任务将在上一个执行完毕之后马上执行; 当任务耗时短于周期,那么正常周期性执行。
scheduleWithFixedDelay()
:此方法用于周期性执行; 无论上一个方法耗时多长,下一个方法都会等到上一个方法执行完毕之后,再经过delay的时间才执行。
下面我以示例的方式验证一下上述的解释是否合理。
schedule()
/**
* @author 王琦 <QQ.Email>1124602935@qq.com</QQ.Email>
* @date 19/10/27 下午3:42
* @description
*/
public class ScheduledServiceTest {
private static final int delay_seconds = 3;
private static final TimeUnit time_unit = TimeUnit.SECONDS;
private static ScheduledExecutorService scheduler;
public ScheduledServiceTest(int nThreads){
scheduler = Executors.newScheduledThreadPool(nThreads, Executors.defaultThreadFactory());
}
public void scheduleTest() throws ExecutionException, InterruptedException {
for (int i = 1; i <= 5; i++) {
int tmp = i;
ScheduledFuture<String> scheduledFuture = scheduler.schedule(new Callable<String>() {
@Override
public String call() throws Exception {
return "scheduleTest"+ tmp;
}
}, delay_seconds, time_unit);
// 通过ScheduledFuture的getDelay方法获取多久后执行
System.out.println("任务"+tmp+"将在 "+scheduledFuture.getDelay(time_unit)+" 秒后执行");
// 通过ScheduledFuture的get方法获取执行返回的结果
System.out.println("任务"+tmp+"执行结果: "+scheduledFuture.get()+"\n");
}
scheduler.shutdown();
System.out.println("scheduler 终止!!");
}
public static void main(String[] args) throws Exception {
ScheduledServiceTest scheduledService = new ScheduledServiceTest(10);
scheduledService.scheduleTest();
}
}
输出结果:
任务1将在 2 秒后执行
任务1执行结果: scheduleTest1
任务2将在 2 秒后执行
任务2执行结果: scheduleTest2
任务3将在 2 秒后执行
任务3执行结果: scheduleTest3
任务4将在 2 秒后执行
任务4执行结果: scheduleTest4
任务5将在 2 秒后执行
任务5执行结果: scheduleTest5
scheduler 终止!!
scheduleAtFixedRate()
public class ScheduledServiceTest {
private static final int delay_seconds = 10;
private static final TimeUnit time_unit = TimeUnit.SECONDS;
private static ScheduledExecutorService scheduler;
public ScheduledServiceTest(int nThreads){
scheduler = Executors.newScheduledThreadPool(nThreads, Executors.defaultThreadFactory());
}
public void scheduleAtFixedRateTest() throws Exception {
Random r = new Random();
SimpleDateFormat format = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
System.out.println("当前时间:"+format.format(System.currentTimeMillis()));
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
String logStart = format.format(System.currentTimeMillis());
System.out.print("> "+logStart);
// 生成10以内的一个随机数,模拟当前任务的执行时间 mockRunTime 秒
int mockRunTime = r.nextInt(20);
try {
Thread.sleep(mockRunTime * 1000);
} catch (InterruptedException e) { }
String logEnd = format.format(System.currentTimeMillis());
String compare = "执行用时" + (mockRunTime >= delay_seconds ? " > 周期时间" : " < 周期时间");
System.out.println(", 任务执行完毕!用时:"+mockRunTime+"秒,当前"+logEnd+",【"+compare+"】");
}
}, 0, delay_seconds, time_unit);
}
public static void main(String[] args) throws Exception {
ScheduledServiceTest scheduledService = new ScheduledServiceTest(100);
scheduledService.scheduleAtFixedRateTest();
}
}
执行结果:
正常预期08秒 > 2019-10-27 17:36:08, 任务执行完毕!用时:10秒,当前2019-10-27 17:36:18,【执行用时 > 周期时间】
正常预期18秒 > 2019-10-27 17:36:18, 任务执行完毕!用时:13秒,当前2019-10-27 17:36:31,【执行用时 > 周期时间】
正常预期28秒 > 2019-10-27 17:36:31, 任务执行完毕!用时:2秒,当前2019-10-27 17:36:33,【执行用时 < 周期时间】
正常预期38秒 > 2019-10-27 17:36:38, 任务执行完毕!用时:12秒,当前2019-10-27 17:36:50,【执行用时 > 周期时间】
正常预期48秒 > 2019-10-27 17:36:50, 任务执行完毕!用时:6秒,当前2019-10-27 17:36:56,【执行用时 < 周期时间】
正常预期58秒 > 2019-10-27 17:36:58, 任务执行完毕!用时:2秒,当前2019-10-27 17:37:00,【执行用时 < 周期时间】
scheduleWithFixedDelay()
/**
* @author 王琦 <QQ.Email>1124602935@qq.com</QQ.Email>
* @date 19/10/27 下午3:42
* @description
* 不管当前任务耗时多久,下个任务必须在当前任务之后完毕后在加上周期时间,才可以开始执行
*/
public class ScheduledServiceTest {
private static final int delay_seconds = 10;
private static final TimeUnit time_unit = TimeUnit.SECONDS;
private static ScheduledExecutorService scheduler;
public ScheduledServiceTest(int nThreads){
scheduler = Executors.newScheduledThreadPool(nThreads, Executors.defaultThreadFactory());
}
public void scheduleAtFixedRateTest() throws Exception {
Random r = new Random();
SimpleDateFormat format = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");
System.out.println("当前时间:"+format.format(System.currentTimeMillis()));
scheduler.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
String logStart = format.format(System.currentTimeMillis());
System.out.print("> "+logStart);
// 生成10以内的一个随机数,模拟当前任务的执行时间 mockRunTime 秒
int mockRunTime = r.nextInt(20);
try {
Thread.sleep(mockRunTime * 1000);
} catch (InterruptedException e) { }
String logEnd = format.format(System.currentTimeMillis());
String compare = "执行用时" + (mockRunTime >= delay_seconds ? " > 周期时间" : " < 周期时间");
System.out.println(", 任务执行完毕!用时:"+mockRunTime+"秒,当前"+logEnd+",【"+compare+"】");
}
}, 0, delay_seconds, time_unit);
}
public static void main(String[] args) throws Exception {
ScheduledServiceTest scheduledService = new ScheduledServiceTest(100);
scheduledService.scheduleAtFixedRateTest();
}
}
执行结果:
当前时间:2019-10-27 17:40:32
> 2019-10-27 17:40:32, 任务执行完毕!用时:10秒,当前2019-10-27 17:40:42,【执行用时 > 周期时间】
> 2019-10-27 17:40:52, 任务执行完毕!用时:3秒,当前2019-10-27 17:40:55,【执行用时 < 周期时间】
> 2019-10-27 17:41:05, 任务执行完毕!用时:4秒,当前2019-10-27 17:41:09,【执行用时 < 周期时间】
> 2019-10-27 17:41:19, 任务执行完毕!用时:12秒,当前2019-10-27 17:41:31,【执行用时 > 周期时间】
> 2019-10-27 17:41:41, 任务执行完毕!用时:15秒,当前2019-10-27 17:41:56,【执行用时 > 周期时间】
> 2019-10-27 17:42:06, 任务执行完毕!用时:16秒,当前2019-10-27 17:42:22,【执行用时 > 周期时间】
看三个执行结果跟上面的解释也是吻合的,我简单分别分析一下三个方法的场景:
schedule()
: 适用于多线程环境下”需要获取任务返回结果“ 的定时任务
scheduleAtFixedRate()
:适用于多线程环境下 ”更关注任务具体执行耗时、间隔“
scheduleWithFixedDelay
:适用于多线程环境下”不关心每次任务的具体执行耗时,只关心固定间隔“
关于ScheduledExecutorService的使用主要就是这三个方法的使用,重要的是根据我们的需要选择合适。 实现原理可以围绕其线程池(构造器中直接调用父类线程池的构造器,到太多东西)和定时器实现两部分学习源码了解。
画了两张图,对应到源码学习思路会更清晰一些:
(1)schedule方法的实现原理示意图:

(2)scheduleAtFixedRate()和scheduleWithFixedDelay()实现原理:

图中提到的队列选用优先队列(小顶堆)而不是有序的数组或者链表?因为优先队列只需要确保局部有序,它的插入、删除操作的复杂度都是O(log n);而有序数组的插入和删除复杂度为O(n);链表的插入复杂度为O(n),删除复杂度为O(1)。即优先队列性能最好。
网友评论