-
2021-03-22 18:08:17
在定时任务中为了加快处理速度,一般都会使用多线程处理业务。需要注意一下事项:
1. 定时任务是否允许上一个定时任务未结束,下一个定时任务可以启动,通过Scheduled中的配置在决定。
2. 主线程已经关闭,线程池中的线程还在运行问题。线程池的关闭方法问题
3. 定时任务有大量数据,导致服务无法停止问题。
4. 如何获取线程的处理结果
如下代码是示例,stop状态的使用和线程池shutdown的处理逻辑需要依据自己的业务来处理。
@PreDestroy
public void destory(){
stop = true;
}
//线程停止状态, 通过注解检测到服务器停止时修改stop状态
boolean stop = false;
//服务器启动后延迟1分钟执行,定时任务结束后延迟1分钟执行下一次
@Scheduled(initialDelay = 60*1000L, fixedDelay = 60*1000L)
public void scheduling(){
List dataList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
dataList.add("data_"+i);
}
int threadSize = 10;
ExecutorService esPool = Executors.newFixedThreadPool(threadSize);
//接收线程的完成时间 或者其他返回结果
CompletionService ecs = new ExecutorCompletionService(esPool);
ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(dataList);
logger.info("===============start {}==================", System.currentTimeMillis());
//启动线程时修改退出线程状态
stop = false;
for (int i = 0; i < threadSize; i++) {
ecs.submit(()->{
long count = 0;
//线程处理加try catch 防止抛出异常中断线程,可能会导致线程池中所有的线程都中断,无可用线程
try{
// !queue.isEmpty()比queue.size()>0效率高很多 .size() 是要遍历一遍集合的
while (!stop && !queue.isEmpty()){
String data = queue.poll();
//500 可以在60秒内完成处理,正常退出
//改成 1000 如果不使用下面的收集结果代码,60秒内无法处理完,会强制shutdown 抛出异常
Thread.sleep(1000L);
logger.info("data {} ok.",data);
count++;
}
}catch (Exception e){
logger.error("",e);
}
//这里范围线程处理的结果
return System.currentTimeMillis()+"_"+count;
});
}
//获取线程的返回结果 会阻塞主线程,直到线程池中所有的线程返回结果
/*try {
for (int i = 0; i < threadSize; i++) {
String threadTime = ecs.take().get();
logger.info("thread run ok time:{}"+threadTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}catch (ExecutionException e) {
e.printStackTrace();
}*/
//关闭线程池
try {
esPool.shutdown();
logger.info("esPoll shutdown:{}", DateUtil.format(new Date(),DateUtil.PATTERN_DEFAULT));
//线程池阻塞,到指定的时间退出,如果所有线程执行完成返回true 否则返回false
boolean await = esPool.awaitTermination(60*1000L,TimeUnit.MILLISECONDS);
logger.info("esPool.awaitTermination 1:{}, {}",await,DateUtil.format(new Date(),DateUtil.PATTERN_DEFAULT));
if(!await) {
stop = true;
await = esPool.awaitTermination(10*1000L,TimeUnit.MILLISECONDS);
logger.info("esPool.awaitTermination 2:{}, {}",await,DateUtil.format(new Date(),DateUtil.PATTERN_DEFAULT));
}
if(!await){
logger.info("wait 60s not stop, shutdownNow");
// 超时的时候向线程池中所有的线程发出中断(interrupted)。
// 让线程池中的所有线程立即中断。 会抛出异常
esPool.shutdownNow();
}
} catch (InterruptedException e) {
//awaitTermination方法被中断的时候也中止线程池中全部的线程的执行。
esPool.shutdownNow();
logger.error("awaitTermination",e);
}
logger.info("===============end {}==================", System.currentTimeMillis());
}
更多相关内容 -
Java多线程之定时任务 以及 SpringBoot多线程实现定时任务以及分享动态实现定时任务
2022-03-07 15:27:20Java多线程之定时任务 以及 SpringBoot多线程实现定时任务、以及分享动态实现定时任务 1. 基于单线程的定时器——简单介绍 Timer 中的 schedule 与 scheduleAtFixedRate 2. 基于多线程的定时器——...Java多线程之定时任务 以及 SpringBoot多线程实现定时任务、以及分享动态实现定时任务
1. 基于单线程的定时器——简单介绍 Timer 中的 schedule 与 scheduleAtFixedRate
1.1 前言
- Timer,一般是用来做延时的任务或者循环定时执行的任务。
使用 Timer 的时候,必须要有一个 TimerTask 去执行任务,这是一个实现了Runnable接口的线程,run 方法里面就是我们自己定义的线程需要做的任务。
1.2 先说 schedule
- 我们简单说4种情况,其实也就两个重载方法:
- ①
timer.schedule(timerTask,0);
,没用延迟,立即执行task,且只执行一次,线程还在。 代码和效果直接看图
- ②
timer.schedule(timerTask,5000);
,延迟5秒执行task,且只执行一次,线程还在。代码和效果直接看图
- ③
timer.schedule(timerTask,0,60000);
,0表示没用延迟,立即执行,然后60000表示每隔1分钟执行一次task。代码和效果直接看图
- ④
timer.schedule(timerTask,5000,60000);
,延迟5秒后执行task,然后每隔1分钟执行一次task,代码和效果直接看图:
附代码:
package com.liu.susu.thread.task.timer; import java.time.LocalDateTime; import java.util.Timer; import java.util.TimerTask; /** * @FileName TimeTaskTest1 * @Description * @Author susu * @date 2022-03-07 **/ public class TimerTaskTest1 { public static void main(String[] args) { Timer timer = new Timer(); TimerTask timerTask = new TimerTask() {//TimerTask 是实现 Runnable 接口的一个线程 @Override public void run() { System.out.println("task-->hello world!-->"+LocalDateTime.now()); } }; System.out.println("begin-->"+LocalDateTime.now()); // timer.schedule(timerTask,0);//没用延迟,立即执行task,且只执行一次,线程还在 // timer.schedule(timerTask,5000);//延迟5秒执行task,,且只执行一次,线程还在 // timer.schedule(timerTask,0,60000);//0表示没用延迟,立即执行,然后60000表示每隔1分钟执行一次task timer.schedule(timerTask,5000,60000);//延迟5秒后执行task,然后每隔1分钟执行一次task } }
1.3 schedule 与 scheduleAtFixedRate 的区别
- 关于上述介绍的 schedule 的4种调用情况,scheduleAtFixedRate 同样也有,最终效果也是一致的,我们这里就不介绍了,说说它两不一样的地方。什么时候不一样呢?当给他们设置指定开始任务的时间时,它们的区别就有了。
主要是public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
方法 与public void schedule(TimerTask task, Date firstTime, long period)
方法的区别。 - 我们先看官网怎么说的:
- 如果不是很明白看我们下面的效果图:
小结
- void schedule(TimerTask task, Date firstTime,long period)方法的任务的计划执行时间是从第一次实际执行任务开始计算的。
如果执行任务的时间没有被延时,那么下一次任务的执行时间参考上一次任务的“开始”时的时间来计算 - scheduleAtFixedRate 会计算首次调用时间,在执行时会先计算出错过的时间内 task 应执行的次数,再去按设定频率去执行。
- 具体原理,还需要大家看源码分析!
附代码
package com.liu.susu.thread.task.timer; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.Date; import java.util.Timer; import java.util.TimerTask; /** * @FileName TimerTaskTest2 * @Description * @Author susu * @date 2022-03-07 **/ public class TimerTaskTest2 { public static void main(String[] args) { Timer timer = new Timer(); TimerTask timerTask = new TimerTask() { @Override public void run() { System.out.println("task-->"+ LocalDateTime.now()); } }; Date firstDateTime = getFirstDateTime(); System.out.println("firstDateTime-->"+firstDateTime+"<=====>now-->"+LocalDateTime.now()+"\n"); timer.schedule(timerTask,firstDateTime,10000); timer.scheduleAtFixedRate(timerTask,firstDateTime,10000); } public static Date getFirstDateTime(){ String dateStr = "2022-03-07 16:30:34"; DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); LocalDateTime localDateTime = LocalDateTime.parse(dateStr, dateTimeFormatter); Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); return date; } }
2. 基于多线程的定时器——ScheduledExecutorService
- ScheduledExecutorService 是一个接口,实现类是ScheduledThreadPoolExecutor,是通过 Executors 自动创建线程池的一种方式(newScheduledThreadPool 方式)
- 关于线程池的创建可以看:
详解Java多线程之线程池. - 关于 ScheduledThreadPoolExecutor,我们下面就简单说3个方法,完整代码如下:
package com.liu.susu.thread.task.pool; import java.time.LocalDateTime; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * @FileName ScheduledThreadPoolTest * @Description * @Author susu * @date 2022-03-07 **/ public class ScheduledThreadPoolTest { public static void main(String[] args) { ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); System.out.println("当前时间是-->"+LocalDateTime.now()); //1.schedule--->只延迟执行,且只执行一次(不循环) executorService.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + LocalDateTime.now()); } },5,TimeUnit.SECONDS);//推迟5秒执行 //2.scheduleAtFixedRate 推迟执行(initialDelay=0,不推迟),然后周期性循环执行task executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + LocalDateTime.now()); try { Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } } },5,10,TimeUnit.SECONDS);//推迟5秒执行,然后每10秒执行一次 //3.scheduleWithFixedDelay 推迟执行(initialDelay=0,不推迟),然后周期性循环执行task executorService.scheduleWithFixedDelay(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + LocalDateTime.now()); try { Thread.sleep(5000); } catch (Exception e) { e.printStackTrace(); } } },5,10,TimeUnit.SECONDS); } }
2.1 executorService.schedule
- 创建并执行在给定延迟后启用的单次操作。
- 效果图:
- 这个没啥可说的了,上面的内容看了的,这个很好理解的,我们主要说一下 scheduleAtFixedRate 与 scheduleWithFixedDelay 的区别。
2.2 scheduleAtFixedRate 与 scheduleWithFixedDelay 区别
- 来我们,先看官方怎么说的
- 这是什么意思呢,我们看看下面的效果就非常明白了
2.2.1 executorService.scheduleAtFixedRate
- 效果图:
- 上面大概明白怎么走的了吧,区别到底在哪里,先别急,看完下面的效果就明白了!
2.2.2 executorService.scheduleWithFixedDelay
- 效果图
- 现在明白二者的区别了吧
2.2.3 小节
- scheduleAtFixedRate:是以固定频率来执行线程任务,固定频率的含义就是可能设定的固定间隔时间不足以完成线程任务,但是它不管,达到设定的延迟时间了就要开始执行下一次任务了。
- scheduleWithFixedDelay:不管线程任务的执行时间的长短,每次都要把任务执行完成后再延迟固定时间(设置的间隔时间)后再执行下一次的任务。
3. 基于多线程的定时器(SpringBoot)
3.1 提前了解 cron表达式和@Async异步
- 看它之前我们先了解一下 cron 表达式:
cron表达式的详细介绍(各域说明以及举例说明). - 还需要了解几个注解
@Async 、 @EnableAsync、 @Scheduled、@EnableScheduling
,我们下面会用到,对这个几个注解不太了解的话,这两个注解(@Async 、 @EnableAsync
)可以看下面这篇文章:
SpringBoot 自定义线程池以及多线程间的异步调用(@Async、@EnableAsync).
3.2 @EnableScheduling 与 @Scheduled
- 关于 @EnableScheduling :
在SpringBoot项目中,在启动类或者定时任务类上添加 @EnableScheduling 注解来开启对定时任务的支持。 - 关于 @Scheduled :
如果在定时任务类中的方法上添加 @Scheduled 注解,则该方法是声明需要执行的定时任务。 - @Scheduled 注解有几种类型参数,先截图下来,我们下面会简单根据例子介绍
3.3 @Scheduled 定时任务例子
- 首先启动类上添加注解
@EnableScheduling
,下面不再提醒了。
3.3.1 关于 cron 表达式的
- cron 表达式不是很清楚的,看我们上面提供的链接,此处不做解释了。
3.3.1.1 cron 例1–>单个任务
- 用注解
@Scheduled(cron = "0/5 * * * * ?")
(每5秒执行一次),代码很简单,直接截图:
- 启动项目后,后台自动按cron表达式配置的执行任务,直接看运行效果:
(1)简单分析@Schedule默认定时任务的线程
- 观察上面的执行结果,不难发现,Spring 的 Schedule 定时任务默认是单线程的。而且是默认所有定时任务都是在一个线程中执行(下面我们有多任务的例子)!
- 这样的话,我们看到的只是代码层次的定时任务,而实际执行得过程是一个任务的开启需要等上一个任务的结束才行。尤其是当系统中有特别耗时的定时任务和执行频繁的定时任务,执行频繁的任务需要等耗时的任务执行完才能执行,你说这算什么定时任务!
- 会造成严重得后果就是如果我们有多个定时任务,一个卡死,其他全挂,嗯,就这样。
- 问题分析出来了,解决是不是就有了,对,异步多线程!
3.3.1.2 cron 例2–>多个任务(非异步)
- 用异步前,我们不妨来写两个定时任务看看效果,先看两个耗时都短的任务:
很明显如果耗时短还不好观察,任务1是按定时5秒执行一次,任务2也按定时2秒执行一次,没看出来,但是能看出来是单线程的执行两个任务!。 - 再来看一个耗时长,一个频繁执行的定时任务,我们让任务1睡一下就行了。
这样对比效果就很明显了吧,这明显不是我们开发中想要的实现方式,所以考虑异步,请往下…
3.3.1.3 cron 例3–>多个任务(异步@Async)
- 把上面的例子改成异步试试,很简单啥也不用,直接异步方法上加注解
@Async
,启动类加@EnableAsync
注解即可。
再说一下,这两个注解不太了解的,看下面文章,此处不做解释了。
SpringBoot 自定义线程池以及多线程间的异步调用(@Async、@EnableAsync). - 修改后的代码如下(在例子3.3.1.2上修改):
- 效果如图:
可以看到在耗时任务1执行的过程中并不影响任务2定时任务的执行,也不影响任务1自己的定时任务,什么意思,意思就是你可以简单理解为同我们上述2.2.1中介绍的executorService.scheduleAtFixedRate
,即以固定频率来执行线程任务,就是在我们任务1中,设定的固定间隔时间5秒,而5秒不足以完成线程任务(需要10秒完成),但是它不管,达到设定的间隔时间5秒后就要开始执行下一次的任务1了(即:开启新的线程来执行)。
3.3.1.4 cron 例4–>多个任务(异步@Async——自定义线程池)
- 3.3.1.3 中,虽然实现了异步,但是 @Async 也存在一个问题,就是默认线程池的问题,这个在这里不做介绍,上面链接的文章里已经说的很清楚,不明白的先了解一下 @Async 。
- 所以,我们用自定义线程池的方式实现异步定时任务
- 代码如下(文字代码上面链接介绍异步的文章里都有,这里截图一下看看,明白逻辑就行了):
- 效果如下:
3.3.1.5 从配置文件读取 cron表达式 以及停掉 cron任务
- 用配置文件的形式,主要方便我们以后更改任务的执行时间等。
- 代码如图:
(1)cron 例5–>用符号 “ - ”控制停止定时任务
注意.properties 与.yml的区别写法
- ① application.properties 中的 cron 表达式不能用双引号引起,而.yml中可以用双引号引起;
- ② 在配置文件里配置让线程任务停掉,不是简单的注释掉,而是用减号符号 “-”,需注意的是两中配置文件写法不一样,yml中必须要加双引号(因为 - 在yml中是一个特殊的字符),application.properties 必须不能用双引号,这点需要注意。
(2)cron 例6–>手动控制定时任务的开启和停止
a. 分析 @EnableScheduling
- 配置之前,我们先来分析一下源码,为什么启动类上加上
@EnableScheduling
注解就开启了对定时任务的支持了呢?好奇是吧,好奇就点呗,点进去看看…
你如果感兴趣的话,可以继续往后点,自己欣赏源码也是打发时间的一种方式,哈哈哈哈!
快烦了吧,说这么多什么意思呢?意思就是你可以不用这个注解@EnableScheduling
让定时任务跑起来。怎么跑,先听解释: - 其实,任务方法上用的
@Scheduled
注解,是被一个叫做ScheduledAnnotationBeanPostProcessor
(上面最后一张截图里出现的)的类所拦截的,所以,根据源码的实现,我们也可以根据配置,决定是否创建这个 bean,如果有这个 bean ,定时任务正常执行,如果没有这个 bean,@Scheduled 就不会被拦截,那么定时任务肯定不会执行了了,嗯,就是这个道理。请往下看怎么实现:
a. 配置文件设置定时任务的开关
- 首先,启动类上的注解 @EnableScheduling 注释掉。
- 然后,配置文件添加开关,如图:
- 然后,接下来我们说两种方式控制这个bean的创建
- 方式一:接口
Condition
与注解Conditional
结合
两种方式说完,我们再测试效果 - 方式二:直接用注解
@ConditionalOnProperty
- 来,看一下效果吧,定时任务还是那两个任务
ok了,就说这么多吧!
3.3.2 其他类型参数——简单说
- 如果目录里的 1 和 2 都看明白了,关于 @Scheduled 注解的其他参数真的没什么可说的了,比葫芦画瓢的事了,我们就简单举个例子就行了
4. springboot动态定时任务的实现
- 非动态,简单开关控制
关于SpringBoot中定时任务@EnableScheduling的开关设置(针对指定的定时任务可控制) - 动态读取配置的定时任务
SpringBoot定时任务的动态配置处理(动态获取数据库配置的定时任务).
5. 附项目工程代码
- clone代码在下面的链接
由于省事,没有单独创建项目,代码就写在了之前的一个项目里,本次所有的代码(包括异步)都在如图所示的文件夹里:
- Csdn上项目代码:
Java多线程之定时任务 以及 SpringBoot多线程实现定时任务. - 码云上项目代码:
https://gitee.com/liuersusu/springboot_jpa. - GitHub 上项目代码:
https://github.com/liuersusu/springboot_jpa.
- Timer,一般是用来做延时的任务或者循环定时执行的任务。
-
SpringBoot实现用户定制的定时任务(动态定时任务)
2022-03-14 09:40:39我们知道SpringBoot能使用@Scheduled注解来进行定时任务的控制,该注解需要配合Cron表达式以及在启动类上添加@EnableScheduling注解才能使用。 不过我们现在的假定情景并不是程序员设定的定时任务,而是用户可以在...SpringBoot实现用户定制的定时任务(动态定时任务)
文章目录
情景
我们知道SpringBoot能使用@Scheduled注解来进行定时任务的控制,该注解需要配合Cron表达式以及在启动类上添加@EnableScheduling注解才能使用。
不过我们现在的假定情景并不是程序员设定的定时任务,而是用户可以在我们的网页上定制定时任务,前端将该任务的信息发送到后端后,后端可以将此任务存入数据库并在规定的时间内执行。例如用户可以设定定时任务的执行时间段,执行时刻等,并可以随时新增、删除和改变定时任务。
接下来我们来使用SpringBoot实现这个假定情景实现
实体类Cron
我们需要创建实体类Cron代表定时任务,这里假设Cron有如下属性:执行时刻、任务标题、任务开始的日期、任务截止日期,以及存入数据库所需要的几个基本属性:id(作为主键)、创建时间、更新时间、状态status
我们用一个BaseEntity来保存基本属性,Cron将继承BaseEntity,使用MyBatisPlus作为ORM框架,Cron的代码如下:@Data @EqualsAndHashCode(callSuper = true) public class Cron extends BaseEntity { private static final long serialVersionUID = 1L; @NotNull(message = "执行时刻不能为空") private LocalTime executeTime; @NotBlank(message = "标题不能为空") private String title; @NotNull(message = "截止日期不能为空") private LocalDate deadTime; @NotNull(message = "开始日期不能为空") private LocalDate startTime; }
这里需要注意的是lombok的@Data注解相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集。
其中,@EqualsAndHashCode注解会生成equals(Object other) 和 hashCode()方法。我们重写了equals(Object other) 和 hashCode()方法,就是为了在两个对象的属性相同时equals能返回true,认为它们两个相同。但@EqualsAndHashCode默认仅使用该类中定义的属性且不会调用父类的equals(Object other) 和 hashCode()方法。这是什么意思呢?仅使用该类中的属性,也就是如果该类的两个对象属性相同,即使这两个对象对应父类的属性不同,equals也会认为它们两个对象相同,从而返回true。默认的实现中不使用父类的属性,将会导致问题,比如,有多个类有相同的部分属性,恰好id(数据库主键)在父类中,那么就会存在部分对象在比较时,它们并不相等,却因为lombok自动生成的equals(Object other) 和 hashCode()方法判定为相等,从而导致出错。所以我们在使用@Data时同时需要加上@EqualsAndHashCode(callSuper=true)注解来解决这一问题,加上(callSuper=true),其生成的equals(Object other) 和 hashCode()方法将调用父类的方法,也就是会考虑父类的属性。
加上@EqualsAndHashCode(callSuper=true)就符合我们的要求了,这样即使两个Cron对象,它们的属性相同,但它们在父类中对应的主键不同,equals将认为它们是不同的对象,返回false。对于前端传参,我们需要进行非空验证,我们在实体类中还加入了@NotNull和@NotBlank注解,并且使用message配置提示语句。这两个注解都来自于javax.validation.constraints包,该包内还有另一个常用注解@NotEmpty,@NotEmpty 用在集合上面,一般用来校验List类型(不能注释枚举类型),而且长度必须大于0。@NotBlank 用在String上面,一般用来校验String类型不能为空,而且调用trim()后,长度必须大于0。@NotNull 可用在所有类型上,校验是否为非null。这些注解都需要配合@Validated注解使用,从而检验Controller的入参是否符合规范,例如:
public Result save(@Validated @RequestBody Cron cron)
Cron的父类BaseEntity的代码如下:
@Data public class BaseEntity implements Serializable { @TableId(value = "id", type = IdType.AUTO) private Long id; private LocalDateTime created; private LocalDateTime updated; private Integer status; }
由于实体类需要在网络中传输,所以BaseEntity需要实现Serializable接口,这里使用MyBatisPlus的@TableId注解进行属性与数据库主键的映射。
Service层:接口CronService以及其实现类CronServiceImpl
我们需定义接口CronService来实现用户定制定时任务需求。用户能创建、删除、修改定时任务,创建任务即判断当前日期是否为既定的执行日期,若是则启动定时任务。删除任务即判断该任务是否已被启动,若是,则将其停止。修改任务即先停止该任务,再重新启动该任务。我们让CronService继承MyBatisPlus的IService接口,对应的数据库操作直接在Controller层中调用相应方法即可,我们就不需要再在CronService中定义了。于是,我们需要在CronService中定义
startCron(Cron cron)
、stopCron(Cron cron)
、changeCron(Cron cron)
三个方法,分别对应用户的创建、删除、修改定时任务操作。public interface CronService extends IService<Cron> { void startCron(Cron cron); void stopCron(Cron cron); void changeCron(Cron cron); }
我们创建CronService的实现类CronServiceImpl来实现上述3个方法。
对于每个定时任务,我们肯定是让线程池提供一个线程去执行它,springboot提供了ThreadPoolTaskScheduler
,可以很方便地对重复执行的任务进行调度管理;相比于通过java自带的周期性任务线程池ScheduleThreadPoolExecutor
,ThreadPoolTaskScheduler
支持根据cron表达式创建周期性任务,这正是我们所需要的。其实ThreadPoolTaskScheduler
底层也是通过线程池ScheduleThreadPoolExecutor
实现的,不过多加了一些支持Cron表达式的代码。ThreadPoolTaskScheduler
的核心成员变量是ScheduledExecutorService scheduledExecutor
,一个ExecutorService
可以安排任务在给定的延迟后运行,或者定期执行。ScheduledFuture
表示可以取消的延迟结果动作。 通常,ScheduledFuture
是使用ScheduledExecutorService
执行任务的返回结果。因此,我们使用
ThreadPoolTaskScheduler
来启动线程,执行定时任务。但这还不够,我们有很多定时任务,我们必须保存它们的信息,以便查找,因为我们还有停止任务和更新任务操作。于是我们可以创建一个HashMap来保存定时任务的信息,key肯定是cron的id,value为ScheduledExecutorService
执行任务的返回结果ScheduledFuture
。我们可以调用ScheduledFuture
的cancel
方法来终止任务的执行。接下来我们来考虑CronService接口3个方法的具体实现。对于startCron方法,我们需要避免它重复启动已经启动的任务,因此我们要先判断该任务是否已经在HashMap中,若不在,我们再去判断当前日期是否在执行日期范围内,若在,我们通过Cron的执行时刻属性构造cron表达式,创建实现了Runnable接口的内部类来实现任务要做的事,调用
ThreadPoolTaskScheduler
的schedule
方法启动该任务,并将该任务存入HashMap中。
对于stopCron方法,我们通过Cron的id从HashMap中查找其对应的ScheduledFuture
,若不为空,则调用其cancel(true)
方法停止任务,并将其从HashMap中删除。cancel方法的参数传入true会中断线程停止任务,而传入false则会让线程正常执行至完成。
changeCron方法的实现很简单,先调用stopCron,再调用startCron即可CronServiceImpl的完整代码如下:
@Service public class CronServiceImpl extends ServiceImpl<CronMapper, Cron> implements CronService { private Logger log = LoggerFactory.getLogger(getClass()); @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; private Map<Long, ScheduledFuture<?>> futureMap = new HashMap<>(); @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { return new ThreadPoolTaskScheduler(); } @Override public void startCron(Cron cron) { if (futureMap.containsKey(cron.getId())) { log.warn("已经存在重复任务,任务id:{},任务标题:{},任务提醒时刻:{},任务开始时间:{},任务截止时间:{}", cron.getId(), cron.getTitle(), cron.getExecuteTime(), cron.getStartTime(), cron.getDeadTime()); return; } if (LocalDate.now().isEqual(cron.getStartTime()) || LocalDate.now().isEqual(cron.getDeadTime()) || (LocalDate.now().isAfter(cron.getStartTime()) && LocalDate.now().isBefore(cron.getDeadTime()))) { LocalTime executeTime = cron.getExecuteTime(); String cronExp = StringUtils.join(Integer.valueOf(executeTime.getSecond()).toString(), " ", Integer.valueOf(executeTime.getMinute()).toString() , " ", Integer.valueOf(executeTime.getHour()).toString(), " * * ?"); ScheduledFuture<?> future = threadPoolTaskScheduler.schedule(new MyRunnable(cron), new CronTrigger(cronExp)); futureMap.put(cron.getId(), future); log.info("启动定时任务成功,任务id:{},任务标题:{},任务提醒时刻:{},任务开始时间:{},任务截止时间:{}", cron.getId(), cron.getTitle(), cron.getExecuteTime(), cron.getStartTime(), cron.getDeadTime()); } } @Override public void stopCron(Cron cron) { ScheduledFuture<?> future = futureMap.get(cron.getId()); if (future != null) { future.cancel(true); futureMap.remove(cron.getId()); log.info("关闭定时任务成功,任务id:{},任务标题:{},任务提醒时刻:{},任务开始时间:{},任务截止时间:{}", cron.getId(), cron.getTitle(), cron.getExecuteTime(), cron.getStartTime(), cron.getDeadTime()); } } @Override public void changeCron(Cron cron) { stopCron(cron);// 先停止,在开启. startCron(cron); } private class MyRunnable implements Runnable { private Cron cron; public MyRunnable(Cron cron) { this.cron = cron; } @Override public void run() { // 定义任务要做的事,完成任务逻辑 } } }
其实我们这样做还没有完成需求,因为在startCron中,只有当前时间在执行时间段内,才会创建线程去执行定时任务,这样是肯定不行的。我们还需要创建一个定时任务管理器,让它每天定时去启动数据库中尚未启动的定时任务,并删除已经过期的定时任务,防止数据积压。
定时任务管理器CronManageTask
这时我们就需要用@Scheduled注解了,我们定义CronManageTask中的cronManage()方法,加上@Scheduled注解,让它每天定时去启动数据库中尚未启动的定时任务,并停止并删除已经过期的定时任务。
使用@Scheduled注解需要注意几个点,一是CronManageTask需使用@Component注解,且此类中不能包含其他带任何注解的方法;二是cronManage()方法不能有参数、不能有返回值;三是需添加@EnableScheduling注解到启动类上面。
违反上述任一点,@Scheduled注解就不会生效CronManageTask的代码如下:
@Component public class CronManageTask { private Logger log = LoggerFactory.getLogger(getClass()); @Autowired private CronService cronService; @Scheduled(cron = "0 0 3 * * ?") public void cronManage() { List<Cron> list = cronService.list(); list.forEach(c -> { if (LocalDate.now().isAfter(c.getDeadTime())) { cronService.stopCron(c); cronService.removeById(c.getId()); log.info("删除过期定时任务成功,任务id:{},任务标题:{},任务提醒时刻:{},任务开始时间:{},任务截止时间:{}", c.getId(), c.getTitle(), c.getExecuteTime(), c.getStartTime(), c.getDeadTime()); } else { log.info("尝试启动尚未start的定时任务,任务id:{},任务标题:{},任务提醒时刻:{},任务开始时间:{},任务截止时间:{}", c.getId(), c.getTitle(), c.getExecuteTime(), c.getStartTime(), c.getDeadTime()); cronService.startCron(c); } }); } }
cron表达式
"0 0 3 * * ?"
表示每天凌晨3点执行。需要注意的是,@Scheduled注解的cron表达式一般都要定义在配置文件里,方便修改,使用cron = "${xiaolinbao.cron}"
,并在application.yml中配置xiaolinbao.cron=0 0 3 * * ?
即可。上面的代码偷懒了。至此,使用SpringBoot实现动态定时任务的需求就完成了
-
多实例下的定时任务如何避免重复执行——分布式定时任务
2020-09-22 11:50:31前面的章节,用户通过绑定手机号的注册为会员,并可以补充完个人...常见的定时任务的解决方案有以下几种: 右半部分基于 Java 或 Spring 框架即可支持定时任务的开发运行,左侧部分需要引入第三方框架支持。针对不...前面的章节,用户通过绑定手机号的注册为会员,并可以补充完个人信息,比如姓名、生日等信息,拿到用户的生日信息之后,就可以通过会员生日信息进行营销,此处就涉及到定时任务执行营销信息推送的问题。本篇就带你走入微服务下的定时任务的构建问题。
定时任务选型
常见的定时任务的解决方案有以下几种:
右半部分基于 Java 或 Spring 框架即可支持定时任务的开发运行,左侧部分需要引入第三方框架支持。针对不同方案,作个简单介绍
- XXL-JOB 是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。任务调度与任务执行分离,功能很丰富,在多家公司商业产品中已有应用。官方地址:https://www.xuxueli.com/xxl-job/
- Elastic-Job 是一个分布式调度解决方案,由两个相互独立的子项目 Elastic-Job-Lite 和 Elastic-Job-Cloud 组成。Elastic-Job-Lite 定位为轻量级无中心化解决方案,依赖 Zookeeper ,使用 jar 包的形式提供分布式任务的协调服务,之前是当当网 Java 应用框架 ddframe 框架中的一部分,后分离出来独立发展。
- Quartz 算是定时任务领域的老牌框架了,出自 OpenSymphony 开源组织,完全由 Java 编写,提供内存作业存储和数据库作业存储两种方式。在分布式任务调度时,数据库作业存储在服务器关闭或重启时,任务信息都不会丢失,在集群环境有很好的可用性。
-
linux 系统定时任务 服务 详解
2021-08-18 13:58:03文章目录Centos 7 定时服务详解介绍1....在企业中,存在很多数据需要备份,那么我们如何让这些数据,每天晚上23:59 自动备份呢? 今天呢,我结合部分实践案列,分享一篇关于定时任务的知识点。 Linux下面有atd和crond -
Spring Boot 整合定时任务,可以动态编辑的定时任务
2022-03-09 20:10:15不过我们当时自己写的这个不支持分布式环境,想要支持倒也不是啥难事,弄一个 zookeeper 或者 redis 作为公共的信息中心,里边记录了定时任务的各种运行情况,有了这个就能支持分布式环境了。 今天咱们不自己写了,... -
分布式定时任务开源方案
2022-03-25 23:35:29整理了常用的几种分布式定时任务开源方案的优缺点对比。 -
有个定时任务突然不执行了,别急,原因可能在这
2021-04-23 21:31:41问题描述程序发版之后一个定时任务突然挂了!“幸亏是用灰度跑的,不然完蛋了。????”之前因为在线程池踩过坑,阅读过ThreadPoolExecutor的源码,自以为不会再踩坑,没想到又一不小心踩坑了,只不过这次的坑踩在了... -
linux 定时任务
2021-05-10 06:21:351. linux的定时任务设置在/etc/crontab文件中,使用命令查看cat /etc/crontab 在/etc目录下有一个crontab文件,这里存放有系统运行的一些调度程序。每个用户可以建立自己的调度crontab。2. 定时任务的用户设置在/etc... -
定时任务的实现
2022-01-26 17:05:42定时任务,cron表达式,ScheduledThreadPoolExecutor,quartz -
定时任务的多机处理问题常用方案
2019-08-13 22:04:04定时任务 -
linux系统添加定时任务
2021-05-17 22:04:47二、具体步骤以及需要注意的地方(本人查找过多人的博客,加上亲身实践,得出以下相对靠谱点的解决问题的方式,如有不足之处,希望大神可以指出):1、写一个简单的可执行shell脚本;(主要是实现 :检... -
SpringBoot 如何执行定时任务
2021-03-29 10:10:56工作中有需要应用到定时任务的场景,一天一次,一周一次,一月一次,一年一次,做日报,周报,月报,年报的统计,以及信息提醒,等,spring boot 提供了一个两种方式实现定时任务。 一、静态定时任务—基于注解 ... -
Java 定时任务-最简单的3种实现方法
2022-03-07 16:01:27一、Timer ...Timer是JAVA自带的定时任务类,实现如下: publicclassMyTimerTask{ publicstaticvoidmain(String[]args){ //定义一个任务 TimerTasktimerTask=newTimerTask(){ @Override publicv... -
定时任务调度及优化
2021-11-24 19:30:48这种设计存在很大的一个问题,定时任务触发的时候,都是一个很大的峰值,这个时候对服务器资源消耗最大,而且存在很多空闲的时间是没有任务处理的状态。争对这个问题,进行了分析和思考。 方案1:使用延时队列,每... -
分布式定时任务原理以及解决方案-指定时间执行定时任务
2020-06-30 10:31:35分布式定时任务原理以及实现 一、单机指定时间执行定时任务实现方式 Timer运行机制 ScheduledThreadPoolExecutor的运行机制 原理图 Leader/Follower模式正在上传…重新上传取消 Timer和... -
golang 多机定时任务管理系统
2018-01-30 10:41:46·cron+,秒级定时,使任务执行更加灵活; ·任务列表文件路径可以自定义,建议使用版本控制系统; ·内置日志和监控系统,方便各位同学任意扩展; ·平滑重加载配置文件,一旦配置文件有变动,在不影响... -
Java定时任务调度详解
2021-04-07 08:32:07前言在实际项目开发中,除了Web应用、SOA服务外,还有一类不可缺少的,那就是定时任务调度。定时任务的场景可以说非常广泛,比如某些视频网站,购买会员后,每天会给会员送成长值,每月会给会员送一些电影券;比如在... -
laravel 定时任务用法及原理解析
2021-07-17 22:28:56一个网站系统往往会有很多定时任务要执行。例如推送订阅消息,统计相关数据等,Linux一般采用crontab对定时任务进行设置和管理,但是随着任务的增多,管理定时任务就比较麻烦,容易管理混乱。laravel 对此的解决方案... -
Java如何实现定时任务?
2022-03-26 21:01:32看完这篇文章你会了解到什么是定时任务,以及为...当时并不知道定时任务有什么实际作用,所以在初学阶段的我,从来没使用过Timer来实现定时的功能。 再后来,我学到并发了。那时候的讲师提到了ScheduledExecutorServic -
Java定时任务的三种实现方式
2021-03-22 17:52:09很多业务需求的实现都离不开定时任务,例如,每月一号,移动将清空你上月未用完流量,重置套餐流量,以及备忘录提醒、闹钟等功能。java 系统中主要有三种方式来实现定时任务:Timer和TimerTask... -
多任务定时关机多任务定时关机
2009-07-29 23:37:02多任务定时关机,可以设置提醒时间,多个任务,很好的东东多,任务定时关机,可以设置提醒时间,多个任务,很好的东东 -
ThreadPoolTaskScheduler实现动态管理定时任务
2021-12-20 21:37:04最近,有个项目有需要用到定时任务,所以做了一个动态管理定时任务的模块。本文将从项目背景、需求、选型、思路、具体实现等方面展开介绍。 背景:有个支付类的项目,中间会产生一些中间态的订单,需要有个定时... -
任务调度开源框架Quartz动态添加、修改和删除定时任务
2014-07-07 14:56:09Quartz 是个开源的作业调度框架,为在 Java 应用程序中进行作业调度提供了简单却强大的机制。Quartz框架包含了调度器监听、作业和触发器监听。...Quartz在功能上远远超越了JDK自带的Timer,很好很强大! -
springboot定时任务
2020-08-05 11:43:35二、基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。 三、基于注解设定多线程定时任务 一... -
Android定时任务的实现方式
2021-06-05 16:15:59在日常的Android功能开发中,我们难免会碰到需要定时任务功能,例如定时轮询某个接口,或者是每隔多长时间检查一次本地数据,在本文中,主要介绍android中常用的两种定时任务实现方式使用Handler实现使用RxJava实现... -
Quartz定时任务
2019-01-27 17:25:351.任务job job就是想要实现的任务类,每一个job必须实现job接口,且实现接口中的 excute()方法。 2.触发器Trigger Trigger为你执行任务的触发器,可以设置特定时间执行该任务 Trigger主要包含SimpleTrigger和... -
Linux配置定时任务
2022-03-17 19:59:33主要介绍Linux中如何使用crondtab工具,包含crondtab定时任务的时间配置以及定时发送get和post请求的方式。 -
【定时任务】Spring Boot 定时执行任务详解,每天定时几点钟执行任务
2020-06-10 19:35:18二、基于接口(SchedulingConfigurer) 前者相信大家都很熟悉,但是实际使用中我们往往想从数据库中读取指定时间来动态执行定时任务,这时候基于接口的定时任务就派上用场了。 三、基于注解设定多线程定时任务 一... -
【SpringBoot】25、SpringBoot中使用Quartz管理定时任务
2020-07-21 15:55:47定时任务在系统中用到的地方很多,例如每晚凌晨的数据备份,每小时获取第三方平台的 Token 信息等等,之前我们都是在项目中规定这个定时任务什么时候启动,到时间了便会自己启动,那么我们想要停止这个定时任务的...