微信公众号:java排坑日记,当你厌烦了长篇大论的面经,没时间系统的读书,可以利用茶余饭后地铁上马桶上几分钟的碎片时间来提升一下自己,坚持下来会有惊喜!
最近记录了一些java中常踩的坑、设计思路和小知识点,大家可以看看
详细记录一次接入xxl-job的踩坑路径
30s快速解决循环依赖
idea中一个小小的操作竟能解决如此多的问题
docker中的服务接入xxljob需要注意的一点
关于一次fullgc的告警分析
mysql中的int类型竟变成了它?
jpa中的字段总是自己莫名更新?
获取不到类上的注解?空指针?
学会这招,再也不怕依赖冲突!
redis的热点key还能这么处理?
领导让我设计一个任务系统
当服务重启时,大部分人没考虑这点
参数还能这么优雅校验?
文件上传报错,全局异常处理!
常见的点赞功能如何实现,如何防止刷赞
但是这次定时任务导致的fullgc引发了我的思考,其实在当今分布式应用的大环境下,我们还用之前老一套的定时任务去处理,确实会带来很多不便,比如这次的问题,如果采用分布式任务调度框架的话,是不是就能避免或者更早的发现这样的问题了?
所以决定调研一下定时任务以及分布式调度框架相关的东西,选择一个适合我们业务的框架。
以下按照技术的发展顺序,依次介绍
早期没有任何框架的时候,是使用JDK中的Timer机制和多线程机制(Runnable+线程休眠)来实现定时或者间隔⼀段时间执⾏某⼀段程序。
import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;
import java.text.SimpleDateFormat;
class MyTask extends TimerTask
{
@Override
public void run()
{
Date date =new Date();
SimpleDateFormat ft=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
System.out.println("当前时间:"+ft.format(date));
}
}
class TimerDemo
{
public static void main(String[] args)
{
Timer t=new Timer();
MyTask task=new MyTask();
t.scheduleAtFixedRate(task,0, 1000);
}
}
其实就是在主线程中开启一个子线程,按时的执行某个任务。
他也有缺点
package com.example.demo.test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
/**
* description
* * @author luhui
* @date 2022/8/17
*/
public class TimerTest {
public static void main(String[] args) {
TimerTask task1 = new TimerTask() {
@Override
public void run() {
System.out.println("task1:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
throw new RuntimeException();
}
}
};
TimerTask task2 = new TimerTask() {
@Override
public void run() {
System.out.println("task2:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
throw new RuntimeException();
}
}
};
Timer timer = new Timer();
timer.schedule(task1, 0, 1000);
timer.schedule(task2, 0, 1000);
}
}
task1与task2的调度间隔为1s,但是因为task1与task2的执行时间都超过了1s,所以预期的时间间隔应该是
task1:2022-08-17 17:06:59
task2:2022-08-17 17:07:01
task1:2022-08-17 17:07:04
task2:2022-08-17 17:07:06
task1:2022-08-17 17:07:09
task2:2022-08-17 17:07:11
输出间隔都为5s,很显然task1和task2互相阻塞。
其实在编译的时候编译器也会提醒

承接上文,直接上代码
package com.example.demo.test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* description
*
* @author luhui
* @date 2022/8/17
*/
public class ScheduledExecutorTest {
public static void main(String[] args) {
Runnable task1 = () -> {
System.out.println("task1:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
throw new RuntimeException();
}
};
Runnable task2 = () -> {
System.out.println("task2:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
throw new RuntimeException();
}
};
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(2);
scheduledExecutorService.scheduleAtFixedRate(task1, 0, 1, TimeUnit.SECONDS);
scheduledExecutorService.scheduleAtFixedRate(task2, 0, 1, TimeUnit.SECONDS);
}
}
执行结果如下
task1:2022-08-17 17:09:54
task2:2022-08-17 17:09:54
task1:2022-08-17 17:09:56
task2:2022-08-17 17:09:57
task1:2022-08-17 17:09:58
task2:2022-08-17 17:10:00
task1:2022-08-17 17:10:01
因为我们在new ScheduledThreadPoolExecutor的时候已经指定了两个核心线程了,所以不会出现单线程的问题了。
Quartz是很常用很经典的一个任务框架,使用起来也很简单,比较容易集成到项目中。
单机项目就不用说了,在分布式情况下,其实Quartz也是可以做成集群的。
但是它的集群有以下缺点:
缺点

对我们的业务场景而言,分布式调度框架的优点就是
这里没有写每种分布式框架的原理,其实分布式调度框架的目的是一样的,所以注定了它的原理不会有太大的差异。
基于这种原理,我们也可以研制自己的定时任务框架,只不过对于大部分场景而言,没必要重复造轮子。