1、Timer
在认为调度线程池功能加入之前,可以使用java.util.Timer来实现定时功能。
-
优点:简单易用
-
缺点:由于所以认为都由同一个线程调度,因此所有任务都是串行执行
- 前一个认为的延迟或者异常将会影响到之后的任务
-
延迟示例:
import lombok.extern.slf4j.Slf4j; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.TestTimer01") public class TestTimer01 { public static void main(String[] args) { Timer timer = new Timer(); TimerTask task1 = new TimerTask() { @Override public void run() { log.debug("task 1..."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }; TimerTask task2 = new TimerTask() { @Override public void run() { log.debug("task 2..."); } }; timer.schedule(task1, 1000); timer.schedule(task2, 1000); } } // 输出 2021-12-16 09:04:57.752 DEBUG [main] c.TestTimer01 - start... 2021-12-16 09:04:58.769 DEBUG [Timer-0] c.TestTimer01 - task 1... 2021-12-16 09:05:00.776 DEBUG [Timer-0] c.TestTimer01 - task 2...
- 任务2并没有如预期的延迟1s后开始执行,而是等待任务1执行完毕在执行
-
异常示例
import lombok.extern.slf4j.Slf4j; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.TestTimer02") public class TestTimer02 { public static void main(String[] args) { Timer timer = new Timer(); TimerTask task1 = new TimerTask() { @Override public void run() { log.debug("task 1..."); int i = 1 / 0; } }; TimerTask task2 = new TimerTask() { @Override public void run() { log.debug("task 2..."); } }; log.debug("start..."); timer.schedule(task1, 1000); timer.schedule(task2, 1000); } } // 输出 2021-12-16 09:07:13.722 DEBUG [main] c.TestTimer02 - start... 2021-12-16 09:07:14.727 DEBUG [Timer-0] c.TestTimer02 - task 1... Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero
- 任务1异常,程序中断,并没有执行任务2
2、ScheduledThreadPoolExecutor
2.1、延迟执行任务
那么我们用ScheduledThreadPoolExecutor改造上面的示例,看看是否解决之前的问题?
import lombok.extern.slf4j.Slf4j;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.TestScheduledThreadPool01")
public class TestScheduledThreadPool01 {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
log.debug("start...");
pool.schedule(() -> {
log.debug("task 1...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("task 1 continue...");
int i = 1 / 0;
log.debug("task 1 end...");
}, 1, TimeUnit.SECONDS);
pool.schedule(() -> {
log.debug("task 2...");
}, 1, TimeUnit.SECONDS);
}
}
// 输出
2021-12-16 09:27:27.703 DEBUG [main] c.TestScheduledThreadPool01 - start...
2021-12-16 09:27:28.752 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool01 - task 1...
2021-12-16 09:27:28.752 DEBUG [pool-2-thread-2] c.TestScheduledThreadPool01 - task 2...
2021-12-16 09:27:30.756 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool01 - task 1 continue...
我们发现问题得到解决,注意:如果线程池线程数设置为1 那么任务换是会串行执行。
但是我们又发现,明明任务1会出现执行异常,我们没有处理异常,但是异常不见了,并且任务没有结束,那么如何处理线程池异常呢?
2.2、定时执行任务
-
scheduleAtFixedRate(任务, 初始延时, 执行周期, 时间单位):执行周期就是每隔多次时间执行任务
-
scheduleWithFixedDelay(任务, 初始延时, 任务间隔, 时间单位):任务间隔为上次执行任务到本次执行任务之间的时间差
-
示例:
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @Slf4j(topic = "c.TestScheduledThreadPool02") public class TestScheduledThreadPool02 { public static void main(String[] args) { ScheduledExecutorService pool = Executors.newScheduledThreadPool(2); log.debug("start..."); method1(pool); } public static void method1(ScheduledExecutorService pool) { pool.scheduleAtFixedRate(() -> { log.debug("task 1 running..."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }, 1, 1, TimeUnit.SECONDS); } public static void method2(ScheduledExecutorService pool) { pool.scheduleWithFixedDelay(() -> { log.debug("task 2 running..."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } }, 1, 1, TimeUnit.SECONDS); } } // 方法1输出结果 2021-12-16 09:46:05.269 DEBUG [main] c.TestScheduledThreadPool02 - start... 2021-12-16 09:46:06.343 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool02 - task 1 running... 2021-12-16 09:46:08.348 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool02 - task 1 running... 2021-12-16 09:46:10.354 DEBUG [pool-2-thread-2] c.TestScheduledThreadPool02 - task 1 running... // 方法2输出结果 2021-12-16 09:49:49.959 DEBUG [main] c.TestScheduledThreadPool02 - start... 2021-12-16 09:49:51.015 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool02 - task 2 running... 2021-12-16 09:49:54.024 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool02 - task 2 running... 2021-12-16 09:49:57.033 DEBUG [pool-2-thread-2] c.TestScheduledThreadPool02 - task 2 running...
- 可以发现方法1任务间隔为2s,方法2任务间隔为3s
3、线程池异常处理
-
方式1:任务自身处理异常,用try…catch…处理异常
-
方式2:如果自己不处理,可以借助Future对象处理
-
示例:
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.*; @Slf4j(topic = "c.TestScheduledThreadPool01") public class TestScheduledThreadPool03 { public static void main(String[] args) throws ExecutionException, InterruptedException { ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); log.debug("start..."); ScheduledFuture<?> future = pool.schedule(() -> { log.debug("task 1..."); int i = 1 / 0; return true; }, 1, TimeUnit.SECONDS); System.out.println(future.get()); } } // 输出 2021-12-16 09:56:21.398 DEBUG [pool-2-thread-1] c.TestScheduledThreadPool01 - task 1... Exception in thread "main" java.util.concurrent.ExecutionException:
4、定时任务应用
需求:我们系统需要每周4 4:00:00点定时维护,那么该如何做呢?
分析:
-
我们使用scheduleAtFixedRate方法
-
可以确定是任务周期为1周,那么初始延迟和时间单位如何确定呢?
-
既然任务周期为1周,那么我们需要知道当前时间和周4的时间间隔,即初始延时
-
当前时间可能在本周的周4之前,也可能之后。
-
为方便时间计算,我们用LocalDateTime类,既然要计算时间,那么单位为毫秒
代码:
import lombok.extern.slf4j.Slf4j;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 定期维护:每周4 4:00:00执行维护工作
* @author Administrator
*/
@Slf4j(topic = "c.RegularMaintenance")
public class RegularMaintenance {
public static void main(String[] args) {
// 1、获取当前时间
LocalDateTime now = LocalDateTime.now();
// 2、确定第一个周4的时间
LocalDateTime time = now.withHour(4).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
// 如果当前时间晚于本周4 4:00:00,那么计算得出的时间在延后1周即可
if (now.compareTo(time) > 0) {
time = time.plusWeeks(1);
}
// 3、执行定时任务
long initialDelay = Duration.between(now, time).toMillis();
long period = 7 * 24 * 3600 * 1000;
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
pool.scheduleAtFixedRate(() -> {
log.debug("定时维护任务开始执行...");
log.debug("定时维护任务结束...");
}, initialDelay, period, TimeUnit.MILLISECONDS);
}
}
参考视频:https://www.bilibili.com/video/BV16J411h7Rd 224~229
源代码仓库地址:https://gitee.com/gaogzhen/concurrent