Java 定时任务

还没真正的遇到使用定时任务的场景,不管怎么说先学起来


1. 定时任务

很多情况下任务并非需要立即执行,而是需要往后或定期执行,这不可能人工去操作,所以定时任务就出现了。项目中肯定会用到使用定时任务的情况,笔者就需要定时去拉取埋点数据


使用定时任务的情况:

  • 每周末凌晨备份数据
  • 触发条件 5 分钟后发送邮件通知
  • 30 分钟未支付取消订单
  • 每 1 小时去拉取数据
  • ......






2. Thread实现

笔试中首次遇到定时任务急急忙忙想出来的方法


2.1 使用

public class ThreadSchedule {
    public static void main(String[] args) {
      
        // 5 秒后执行任务
        int interval = 1000 * 5;

        // 新线程执行
        new Thread(() -> {
            try {
                Thread.sleep(interval);
                System.out.println("执行定时任务");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}


2.2 分析

  • 定时不准确,因依赖底层硬件,Windows误差为10微妙
  • System.currentTimeMillis() 依赖系统硬件,还会受网络时间同步修改
  • System.nanoTime() 依赖 JVM 的运行纳秒数,并不受同步影响,适用于计算准确的时间差
  • 但计算当前日期还是要使用 currentTimeMillis 的格林威治时间,而 nanoTime 计算 JVM 运行时间不准确






3. java.util.Timer

Timer 负责执行计划功能,会启动一个后台线程,而 TimerTask 负责任务逻辑


3.1 使用

public class TimeSchedule {
    public static void main(String[] args) {
        
        // 启动一个守护线程
        Timer timer = new Timer();  
        
        // 定时任务
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("执行定时任务");
            }
        };

        long delay = 0;     		// 延迟执行
        long period = 1000 * 5;  	// 间隔
        timer.scheduleAtFixedRate(timerTask, 1, period);
        // timer.cancel();			   可取消
    }
}


3.2 分析

// new Timer() 最终的构造函数会启动一个线程
public Timer(String name) {
    thread.setName(name);
    thread.start();
}

// 这个线程里面封装了一个 Queue 优先级队列,该线程会去队列里不停执行里面的任务
class TimerThread extends Thread {
    private TaskQueue queue;
    TimerThread(TaskQueue queue) {
        this.queue = queue;
    }
}

// 这个队列里面存放了各种 TimerTask 定时的任务逻辑
class TaskQueue {
    private TimerTask[] queue = new TimerTask[128];
}
  • 只有一个单线程执行,所以是串行执行
  • 某个任务执行时间较长会阻塞后面预定执行的任务,所以时间并不准确
  • 线程报错后续的定时任务直接停止






4. ScheduledExecutorService

java.util.concurrent中的工具类,是一个多线程的定时器


4.1 使用

public class ExecutorSchedule {
    public static void main(String[] args) {
        
        // 定时任务
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("执行定时任务");
            }
        };

        // 线程池执行
        long delay = 0;     		// 延迟执行
        long period = 1000 * 5;  	// 间隔
        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
        service.scheduleAtFixedRate(runnable, delay, period, TimeUnit.MILLISECONDS);
}

笔者最常用的一个定时操作了,之前还写过定时的探测任务







5. Spring的定时任务

需要开启定时功能@EnableScheduling

@Component
public class SpringSchedule {
    
    // cron 表达式,每秒执行一次
    
    @Scheduled(cron = "*/1 * * * * ?")
    public void springSchedule(){
        System.out.println("执行定时任务");
    }
    
}

底层是 ScheduledThreadPoolExecutor 线程池,和上面的 ScheduledExecutorService 是同根同源







6. XXL-JOB

xxl-job 是个人维护的分布式任务调度框架(国人写的,有详细的中文文档),分为 调度中心 和 执行器。执行器就是定时任务,而调度中心则负责管理调用这些定时任务,调度中心也可以存储定时任务通过脚本形式(Java 是 Grovvy)免编译地实时下发到各服务中执行。最重要的是有 UI 界面,用户友好的体验


6.1 建立数据库

xxl-job 的存储是基于数据库的,相对比 quartz 可保存在内存和数据库有一点性能影响。首先第一步就是要建库,在 xxl-job 官网有 SQL 语句 tables_xxl_job.sql,直接执行即可建库建表



6.2 部署 xxl-job-admin 调度中心

从 Git 上拉取最新的代码,然后编译根模块,填好 admin 模块的数据库地址等,即可启动这个调度中心(支持 Docker 部署,更加方便)



6.3 创建定时任务

在需要定时任务的服务中 引入依赖、添加配置、创建定时任务



6.3.1 依赖

<!-- xxl-job-core -->
<dependency>
    <groupId>com.xuxueli</groupId>
    <artifactId>xxl-job-core</artifactId>
    <version>${project.parent.version}</version>
</dependency>

6.3.2 基本配置

# xxl-job admin address
xxl.job.admin.addresses=http://xxx.xxx.xxx:8080/xxl-job-admin

# xxl-job executor appname
xxl.job.executor.appname=xxl-job-executor-demo

6.3.3 定时任务

@Component
public class MyJob {
    
    @XxlJob("MyJob")
    public void MyJob() throws Exception {
        
        // 执行器日志记录
        XxlJobHelper.log("myjob is execute");
        
        // 定时任务逻辑
        System.out.println("myjob is executing");
        
        // default success
    }
}


6.4 执行定时任务

进入调度中心新建一个任务,然后执行定时任务即可(使用的是 RPC 远程过程调用)



6.5 遇到的问题

默认执行器是自动注册到调度中心的,但是时常进去的地址有问题而导致执行失败,所以要手动录入执行器的地址



6.6 分析

作为轻量级的分布式定时任务,有 UI 界面简单方便使用,而且对代码没什么侵入性,已经能满足大部分项目的需求了,笔者如果要用定时任务也会首选 xxl-job



原文地址:https://www.cnblogs.com/Howlet/p/15580411.html