开发中有个需求,在A页面每十秒调用一次A1任务,在B页面每十秒调用一次B1任务,A页面和B页面是互斥的,一次只能展示一个页面。因此,停留在哪个页面就调用哪个页面的任务。
做这个需求,我想到了单线程池+任务调度。
代码很快写好了,执行结果出乎意料。
当我点击A页面时,单线程池加入了A1任务,并开启了定时执行。当我点击B页面时,单线程池又加入了B1任务,并开启了定时执行。
也就是说,虽然我看得当前页面是B页面,但是后台却有两个任务在执行,无形中偷走了电脑的最宝贵资源---性能。
为什么会这样呢?说好的单线程呢?
1. 任务间的调度间隔
如果我们使用的是ScheduledExecutorService,并且提交了多个定时任务,它们的调度可能是在不同的时间点。
即使我们使用的是单线程池,每个任务会依次排队执行,前一个任务执行完毕之后,才会执行下一个任务。
所以,不同的任务可能在不同的时刻被触发执行,这也是我们看到多个任务在池中被执行的原因。
例如:
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
Runnable task1 = () -> {
try {
System.out.println("Task 1 executed");
Thread.sleep(3000); // 模拟长时间任务
} catch (InterruptedException e) {
System.out.println("Task 1 interrupted");
}
};
Runnable task2 = () -> {
try {
System.out.println("Task 2 executed");
Thread.sleep(2000); // 模拟长时间任务
} catch (InterruptedException e) {
System.out.println("Task 2 interrupted");
}
};
// 第一个任务立即执行,第二个任务延迟1秒执行
scheduledExecutorService.schedule(task1, 0, TimeUnit.SECONDS);
scheduledExecutorService.schedule(task2, 1, TimeUnit.SECONDS);
在这个例子中,task1会立即执行,而task2会在task1执行后的1秒钟开始执行。
尽管使用了单线程池,但任务并非同时执行,而是顺序执行。
2. 任务的重复调度
我们也可能设置了一个任务的定期调度,并且在任务执行过程中,它会被重复调度。在这种情况下,多个任务可能在不同时间点被提交到线程池。
例如:
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
Runnable task1 = () -> {
try {
System.out.println("Task 1 executed");
Thread.sleep(3000); // 模拟任务运行时间
} catch (InterruptedException e) {
System.out.println("Task 1 interrupted");
}
};
scheduledExecutorService.scheduleAtFixedRate(task1, 0, 2, TimeUnit.SECONDS); // 每2秒执行一次
在这个例子中,task1将每 2 秒执行一次。由于任务执行时间较长,下一次执行task1会在前一次执行未完成时启动,但由于是单线程池,它们会排队执行。
3. 任务的实际执行时机
在单线程池中,任务的调度时间与实际执行时间之间可能会有延迟,特别是当任务执行时间较长时。
例如,如果我们有多个任务,它们可能会在不同的时机被提交到线程池,但由于是单线程池,任务会按照顺序执行,尽管它们可能在不同的时间点开始执行。
4. 任务的阻塞和等待
如果我们在任务中使用了阻塞方法(例如Thread.sleep()或Object.wait()),它会导致当前任务阻塞,直到方法执行完成。
在单线程池中,只有一个任务会执行,当这个任务正在等待或者休眠时,其他任务需要等它完成。
例如:
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
Runnable task1 = () -> {
try {
System.out.println("Task 1 started");
Thread.sleep(3000); // 模拟任务执行时长
System.out.println("Task 1 finished");
} catch (InterruptedException e) {
System.out.println("Task 1 interrupted");
}
};
Runnable task2 = () -> {
try {
System.out.println("Task 2 started");
Thread.sleep(2000); // 模拟任务执行时长
System.out.println("Task 2 finished");
} catch (InterruptedException e) {
System.out.println("Task 2 interrupted");
}
};
scheduledExecutorService.submit(task1);
scheduledExecutorService.submit(task2);
在这个例子中,task1会先执行,task2必须等到task1执行完成之后才能执行。即使它们的启动时机不同,它们也会在单线程池中依次执行。
好吧,不得不承认,我提交给定时调度器的是任务,而不是线程。
翻看日志再次确认,确实是单线池,不管执行哪个任务都是同一个线程,同一个线程在不同时间节点分别执行了A1任务和B1任务。
单线程池不等于单任务池。这该死的理解力,得抠字眼理解。
最后总结
即使是单线程池,任务也会按照提交顺序依次执行。
可能出现“多个任务在池中执行”的情况,通常是因为任务提交的时机不同、任务的执行时间不一致、或调度策略的原因。
在这种情况下,多个任务可能在不同的时间被触发,并在单个线程上顺序执行,而不是同时执行。
如果我们有定时任务或重复任务调度,也需要特别注意任务执行的间隔和执行时间。
话说,我想要的效果该如何实现呢?那就是取消已经存在的定期调度。
在进入A页面时,看看有没有存在的任务调度,如果有,取消掉,然后再把A1任务加入定时调度器里。B页面同理。问题就是这样解决的。