Timer 定时器

Timer 是 JDK 自带的一个定时器类

基本用法

  1. 创建任务定时器对象
  2. 创建任务
  3. 启动任务
    ```java
    /**
  • 创建定时器示例
  • /
    private static Timer timer = new Timer();

/**

  • 创建任务实现类
  • /
    static class MyTask extends TimerTask {
    @Override
    public void run() {
       System.out.println("定时任务开始执行");
    }
    }

public static void main(String[] args) {
// 启动任务
timer.schedule(new MyTask(), DateUtil.parse(“2024-02-04 16:41”, “yyyy-MM-dd HH:mm”));
}


> 内部原理

1. 在 Timer 中定义了 TaskQueue 任务队列(小顶堆),用于存放任务
2. 定义 TimerThread 线程,并在线程中使用 while 循环持续监测是否有新的任务加入到队列中
3. 队列中的任务根据下一次执行时间 nextExecutionTime 构造小顶堆,每次优先取时间最小的任务进行处理
```java
public class Timer { 
    // 任务队列
    private final TaskQueue queue = new TaskQueue();

    // 调度线程
    private final TimerThread thread = new TimerThread(queue);

    // 构造方法
    public Timer(String name) { 
        thread.setName(name);
        // 启动调度线程
        thread.start();
    } 

    // 添加任务
    public void schedule(TimerTask task, Date time) { 
        sched(task, time.getTime(), 0);
    } 
} 

class TimerThread extends Thread { 
    public void run() { 
        try { 
            mainLoop();
        }  finally { 
            synchronized(queue) { 
                newTasksMayBeScheduled = false;
                queue.clear();  
            } 
        } 
    } 
    private void mainLoop() { 
        while (true) { 
            try { 
                synchronized(queue) { 
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        // 等待任务加入
                        queue.wait();
                    ……
                    task = queue.getMin();
                    ……
                } 
                if (taskFired)
                    // 运行任务
                    task.run();
            }  catch(InterruptedException e) { 
            } 
        } 
    } 
} 

任务执行过程

private void mainLoop() { 
        while (true) { 
            try { 
                TimerTask task;
                boolean taskFired;
                synchronized(queue) { 
                    // 等待队列不为空
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; 

                    long currentTime, executionTime;

                    // 获取时间最近的任务
                    task = queue.getMin();
                    synchronized(task.lock) { 
                        if (task.state == TimerTask.CANCELLED) { 
                            queue.removeMin();
                            continue;
                        } 

                        // 获取当前时间、任务执行时间
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;

                        // 是否已经到达执行时间
                        if (taskFired = (executionTime<=currentTime)) { 
                            if (task.period == 0) { 

                                // period 为0代表只执行一次,移出队列
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            }  else {  

                                // 计算下一次执行时间
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            } 
                        } 
                    } 

                    // 未到达执行时间则等待
                    if (!taskFired) 
                        queue.wait(executionTime - currentTime);
                } 

                // 执行任务(在当前线程中执行,将会阻塞影响后续任务执行)
                if (taskFired)
                    task.run();
            }  catch(InterruptedException e) { 
            } 
        } 
    } 

Quartz 任务调度

Quartz 的核心类有以下三部分:

  • 任务 Job : 需要实现的任务类,实现 execute() 方法,执行后完成任务。
  • 触发器 Trigger : 包括 SimpleTriggerCronTrigger
  • 调度器 Scheduler : 任务调度器,负责基于 Trigger触发器,来执行 Job任务。

image.png

基本使用

1、导入依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-quartz</artifactId>
</dependency>

2、直接在 service 中使用

@Service
public class JobHandler { 

    @Resource
    private Scheduler scheduler;

    @PostConstruct
    private void test() throws SchedulerException { 
        // 1、定义 job
        JobDetail jobDetail = JobBuilder
        .newJob(JobOne.class)
        .withIdentity("jobName", "jobGroup")
        .build();
        // 2、定义触发器
        Trigger trigger = TriggerBuilder.newTrigger()
        .withIdentity("triggerName", "triggerGroup")
        .withSchedule(CronScheduleBuilder.cronSchedule("0/1 * * * * ?").withMisfireHandlingInstructionDoNothing())
        .build();
        // 3、放入调度器
        scheduler.scheduleJob(jobDetail, trigger);
    } 
} 

xxl-job 任务调度

官方文档:https://www.xuxueli.com/xxl-job/#《分布式任务调度平台XXL-JOB》
仓库地址:https://gitee.com/xuxueli0323/xxl-job.git

使用流程

1、下载源码

git clone https://gitee.com/xuxueli0323/xxl-job.git

2、使用 IDEA 打开项目

xxl-job-admin:调度中心
xxl-job-core:公共依赖
xxl-job-executor-samples:执行器Sample示例(选择合适的版本执行器,可直接使用,也可以参考其并将现有项目改造成执行器)
    :xxl-job-executor-sample-springboot:Springboot版本,通过Springboot管理执行器,推荐这种方式;
    :xxl-job-executor-sample-frameless:无框架版本;

3、创建对应的数据库 xxl_job ,执行 sql 文件
image.png
4、修改配置文件中的账号密码信息
image.png
xxl-job-admin 是一个前后端不分离的项目,启动后访问 http://localhost:8080/xxl-job-admin/toLogin 即可进入调度中心页面,账号密码:admin/123456
image.png

执行器配置

任务也是配置在执行器中
1、参考 xxl-job-executor-sample-springboot 示例代码,导入依赖

<!-- http://repo1.maven.org/maven2/com/xuxueli/xxl-job-core/ -->
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>${ 最新稳定版本} </version>
</dependency>

2、修改配置文件

  • xxl.job.admin.addresses:指定调度中心的地址
  • xxl.job.accessToken:调度中心 token
  • xxl.job.executor.appname:执行器名称
  • xxl.job.executor.ip:执行器 ip 可自动获取,可不填
  • xxl.job.executor.port:执行器端口需指定(对应 netty 端口)

调度中心触发任务的时候,会直接向 executor 的 ip + 端口 发送请求,由 netty 对内容进行读取并转发到对应的方法上
image.png
3、新增 XxlJobConfig 配置类,创建 XxlJobSpringExecutor 实例
image.png
4、创建任务,在需要被调度的方法上新增 @XxlJob 注解即可被标识
image.png
5、启动该执行器项目,即可在调度中心中查看到注册进来的调度器了
image.png
6、添加任务,指定执行器、cron、执行方法 即可
image.png

原理分析

xxl-job-admin
admin 中定义了对应的线程用于循环监听 job,其中 JobScheduleHelper 类中定义了一个线程用于循环监听 job 是否临近触发
image.png
在 com/xxl/job/admin/core/thread/JobScheduleHelper.java:80 中循环读取近 5 秒内将触发的任务,然后依次处理并更新下一次执行时间
image.png
所以当我们在后台中新增任务的时候,其实也就是往数据库的 xxl_job_info 新增一条记录而已,这样就可以在 JobScheduleHelper 线程中读取到任务
image.png
image.png

xxl-job-executor-sample-springboot
执行器注册流程
在 XxlJobConfig 配置类中定义了 xxlJobExecutor 这个 bean,所以在项目启动的过程中就会自动实例化一个 XxlJobSpringExecutor 对象
image.png
而在 XxlJobSpringExecutor 实例化过程中则会调用两个初始化方法:
image.png
XxlJobSpringExecutor:用于读取所有包含 XxlJob 注解的方法,并添加到 jobHandlerRepository 这个 map 中
image.png
start:调用父类 XxlJobExecutor 的方法,用于注册执行器,配置 netty 的地址
image.png

任务触发过程

1、在 com/xxl/job/admin/core/thread/JobScheduleHelper.java:80 代码中读取到任务之后,会对任务触发时间进行判断,然后采用不同的触发方式
image.png
2、而触发的任务是交给了 JobTriggerPoolHelper 线程
image.png
3、在 JobTriggerPoolHelper 添加触发任务的时候会根据任务的执行时间判断使用哪个线程池
image.png
这里定义了 fastTriggerPool 和 slowTriggerPool,两个线程的主要不同是 最大线程数量等待队列大小

  • slowTriggerPool:一分钟内,超过 10 运行时间大于 500 毫秒就属于 slowTriggerPool,拥有更大的 线程数量 和 等待队列

image.png