一、JDK 自带定时器
-
在
JDK
的工具类中有一个Timer
类用于定时任务的业务实现 -
根据
Timer.schedule
的重载方法,可以实现不同机制的任务调度 -
接下来演示不同机制都具体表现以及存在都问题和解决方案
二、JDK 自带定时器的实现方式
-
代码实现
/** * Copyright (C), 1998-2021, Shenzhen Rambo Technology Co., Ltd * JDK 自带调度任务 * * 弊端: * 任务调度都采用同一线程池同一线程 * 如果一旦某个任务响应时间过长,将会导致线程阻塞(单线程执行多个任务) * * @author Rambo * @date 2021/2/22 17:05 * @since 1.0.0.1 */ @Slf4j public class TimerScheduleTasks { // 定义任务调度器实例对象 private static final Timer timer = new Timer(); public static void main(String[] args) { timer1(); timer2(); timer3(); timer4(); } /** * 方法一: * schedule(TimerTask task, long delay, long period); * 设定指定任务 task 在指定延迟 delay 后进行固定延迟 period 的执行 * 0ms之后开始执行,每隔 1000 ms执行一次 * * @author Rambo * @date 2021/2/22 17:19 */ public static void timer1(){ timer.schedule(new TimerTask(){ @SneakyThrows @Override public void run(){ log.info("-------------> 调度线程名称:[{}],被调度方法名称:[TimerScheduleTasks.timer1()],执行频率:1秒/次,当前时间:[{}]", Thread.currentThread().getName(), DateUtil.now()); // 模拟业务处理时长 TimeUnit.SECONDS.sleep(5); } }, 0 , 1000); } /** * 方法二: * schedule(TimerTask task, Date time); * 设定指定任务 task 在指定时间 time 后执行一次 * 5ms 后执行一次 run 方法后结束,执行完成后线程处于挂起等待状态 * * @author Rambo * @date 2021/2/22 17:19 */ public static void timer2(){ timer.schedule(new TimerTask(){ @SneakyThrows @Override public void run(){ log.info("-------------> 调度线程名称:[{}],被调度方法名称:[TimerScheduleTasks.timer2()],当前时间:[{}]", Thread.currentThread().getName(), DateUtil.now()); // 模拟业务处理时长 TimeUnit.SECONDS.sleep(5); } }, 5000); } /** * 方法三: * scheduleAtFixedRate(TimerTask task, long delay, long period); * 设定指定任务 task 在指定延迟 delay 后进行固定频率 period 的执行 * 0ms 后,每隔 2s 执行一次 * @author Rambo * @date 2021/2/22 17:39 */ public static void timer3() { timer.scheduleAtFixedRate(new TimerTask() { @SneakyThrows @Override public void run() { log.info("-------------> 调度线程名称:[{}],被调度方法名称:[TimerScheduleTasks.timer3()],执行频率:2秒/次,当前时间:[{}]", Thread.currentThread().getName(), DateUtil.now()); // 模拟业务处理时长 TimeUnit.SECONDS.sleep(5); } }, 0, 2000); } /** * 方法四: * scheduleAtFixedRate(TimerTask task, Date firstTime, long period); * 设定指定的任务 task 在指定的时间 firstTime 开始进行重复的固定速率 period 执行 * 每天在预设时间去执行,执行完成后线程处于挂起等待状态 * * @author Rambo * @date 2021/2/22 17:45 */ public static void timer4(){ Calendar calendar = Calendar.getInstance(); // 控制时 calendar.set(Calendar.HOUR_OF_DAY , 17); // 控制分 calendar.set(Calendar.MINUTE , 51); // 控制秒 calendar.set(Calendar.SECOND , 0); Date time = calendar.getTime(); SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss"); log.info("任务计划执行时间:[{}]", format.format(time)); timer.scheduleAtFixedRate(new TimerTask(){ @SneakyThrows @Override public void run(){ log.info("-------------> 调度线程名称:[{}],被调度方法名称:[TimerScheduleTasks.timer4()],执行频率:每天固定时间执行,当前时间:[{}]", Thread.currentThread().getName(), DateUtil.now()); // 模拟业务处理时长 TimeUnit.SECONDS.sleep(5); } // 这里设定将延时每天固定执行 }, time, 1000 * 60 * 60 * 24); } }
三、JDK 自带定时器的问题描述
-
任务调度都采用同一线程池同一线程
-
如果一旦某个任务响应时间过长,将会导致线程阻塞(单线程执行多个任务)
四、针对问题的解决方案
-
方案一(不推荐:只解决线程问题,没有解决线程阻塞问题)
-
由于上述案例代码中,所有的调度任务都采用同一个
Timer
实例对象,所以多个任务同时协作时,使用的是同一个线程... ... public static void timer2(){ // 每个调度任务都用新的实例去创建 Timer timer = new Timer(); timer.schedule(new TimerTask(){ @SneakyThrows @Override public void run(){ log.info("-------------> 调度线程名称:[{}],被调度方法名称:[TimerScheduleTasks.timer2()],当前时间:[{}]", Thread.currentThread().getName(), DateUtil.now()); // 模拟业务处理时长 TimeUnit.SECONDS.sleep(5); } }, 5000); } ... ...
-
通过以上分析,提出的解决方案是在触发每一个调度任务都采用新的
Timer
实例,则可以采用不同的线程去调度 -
运行效果
弊端
- 每一次调度都采用新的
Timer
实例对象来创建,可以解决同一线程问题,但是阻塞问题并未解决,原因是阻塞方法还是只能通过这个实例来调度
-
-
方案二(推荐)
-
采用
ThreadPoolTaskExecutor
对象自定义线程池的方式结合@Async
注解来实现 -
采用
ThreadPoolTaskScheduler
对象自定义线程池的方式结合@Scheduled
注解来实现 -
以上介绍的两种方式,将在后续文章给出具体实现
-