DemandScheduler - 按需调度器

28 阅读4分钟

DemandScheduler - 按需调度器使用说明

概述

DemandScheduler 是一个智能的、按需启停的任务调度器,与传统固定间隔的调度器不同,它只在有任务需要处理时运行,无任务时自动停止,有效节省系统资源。

核心特性

🎯 按需调度

  • 智能启停:有任务时自动启动,无任务时自动停止
  • 避免空转:消除定时器无任务时的空跑消耗
  • 资源友好:适合任务不频繁但需要及时处理的场景

🔒 线程安全

  • 内置锁机制防止并发问题
  • 原子操作保证状态一致性
  • 支持多线程环境使用

⚙️ 高度可配置

  • 支持自定义调度名称
  • 可配置执行间隔、初始延迟、时间单位
  • 支持自定义任务检查器和执行器

🚀 易用性

  • Builder模式链式调用
  • 清晰的API设计
  • 完善的日志记录

快速开始

1. 基础用法

@Component
public class MyTaskService {
    
    private DemandScheduler demandScheduler;
    
    @PostConstruct
    public void init() {
        // 1. 创建调度器(指定名称用于日志标识)
        demandScheduler = new DemandScheduler("MyTaskScheduler");
        
        // 2. 配置调度参数并设置检查器和执行器
        demandScheduler.configure(30, TimeUnit.SECONDS, 0)  // 每30秒执行一次,立即开始
                .withTaskChecker(this::hasTasksToProcess)    // 任务检查器
                .withTaskExecutor(this::executeTasks)       // 任务执行器
                .start();                                   // 启动调度器
    }
    
    @PreDestroy
    public void destroy() {
        demandScheduler.stopScheduler();  // 应用关闭时停止调度器
    }
    
    // 任务检查器:返回true表示有任务需要处理
    private boolean hasTasksToProcess() {
        // 检查数据库、队列、缓存等
        return taskRepository.hasPendingTasks();
    }
    
    // 任务执行器:具体执行业务逻辑
    private void executeTasks() {
        // 获取并处理任务
        List<Task> tasks = taskRepository.getPendingTasks();
        tasks.forEach(this::processTask);
    }
    
    // 添加新任务时通知调度器
    public void addNewTask(Task task) {
        taskRepository.save(task);
        demandScheduler.notifyTaskAdded();  // 通知有新任务
    }
    
    // 删除任务时通知调度器
    public void delNewTask(Task task) {
        taskRepository.del(task);
        demandScheduler.notifyTaskRemoved();  // 通知删除任务
    }
}

API 详解

构造方法

// 必须指定调度器名称(用于日志标识)
DemandScheduler scheduler = new DemandScheduler("YourSchedulerName");

配置方法

方法参数说明默认值
configure(period, unit, initialDelay)long period, TimeUnit unit, long initialDelay配置调度参数1分钟间隔,0延迟
withTaskChecker(Supplier<Boolean>)返回boolean的函数式接口设置任务检查器必须设置
withTaskExecutor(Runnable)无返回值的函数式接口设置任务执行器必须设置

控制方法

方法说明
start()启动调度器(检查初始任务)
stopScheduler()强制停止调度器并关闭线程池
notifyTaskAdded()通知有新任务添加
notifyTaskRemoved()通知有任务被删除

常见问题

Q1: 调度器没有启动?

检查点:

  1. 是否调用了 start() 方法
  2. 初始检查时 taskSupplier 是否返回 true
  3. 查看日志是否有错误信息

Q2: 任务执行异常会停止调度器吗?

不会。调度器会捕获执行器异常,记录日志后继续运行。

Q3: 如何立即执行任务?

调用 notifyTaskAdded() 方法,调度器会立即启动并执行任务。

Q4: 可以动态修改调度间隔吗?

当前不支持。需要停止后重新创建调度器。

Q5: 支持集群环境吗?

需要额外处理。当前设计是单机版,集群环境需要:

  1. 分布式锁控制任务检查
  2. 共享的任务存储(如Redis、数据库)

总结

DemandScheduler 是一个简单而强大的按需调度工具,特别适合以下场景:

  • ✅ 任务不频繁但需要及时处理
  • ✅ 需要节省系统资源的应用
  • ✅ 按需触发的后台任务
  • ✅ 需要灵活启停的任务调度

通过合理使用,可以有效提升应用性能和资源利用率。

完整代码


@Slf4j
public class DemandScheduler {

    private ScheduledExecutorService scheduler;
    private ScheduledFuture<?> scheduledFuture;
    private final AtomicBoolean isRunning = new AtomicBoolean(false);
    private final Object lock = new Object();

    private Supplier<Boolean> taskSupplier;
    private Runnable taskExecutor;

    private long initialDelay = 0L;
    private long period = 1L;
    private TimeUnit timeUnit = TimeUnit.MINUTES;
    private String schedulerName = "DemandScheduler";

    /**
     * 禁止空参
     */
    private DemandScheduler(){}
    /**
     * 初始化调度器(必须首先调用)
     */
    public DemandScheduler (String name) {
        Assert.notBlank(name);
        this.schedulerName = name;
        scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
            Thread thread = new Thread(r, this.schedulerName);
            thread.setDaemon(true);
            return thread;
        });
        log.info("[{}] 调度器初始化完成", schedulerName);
    }

    /**
     * 配置调度参数
     */
    public DemandScheduler configure(long period, TimeUnit timeUnit, long initialDelay) {
        this.period = period;
        this.timeUnit = timeUnit;
        this.initialDelay = initialDelay;
        return this;
    }

    /**
     * 设置任务检查器
     */
    public DemandScheduler withTaskChecker(Supplier<Boolean> taskSupplier) {
        this.taskSupplier = taskSupplier;
        return this;
    }

    /**
     * 设置任务执行器
     */
    public DemandScheduler withTaskExecutor(Runnable taskExecutor) {
        this.taskExecutor = taskExecutor;
        return this;
    }

    /**
     * 启动调度器(在配置完成后调用)
     * 检查初始任务并决定是否启动
     */
    public DemandScheduler start() {
        checkConfig();
        synchronized (lock) {
            if (hasTasks() && isRunning.compareAndSet(false, true)) {
                log.info("[{}] 初始检测到任务,启动定时任务", schedulerName);
                doStartScheduledTask();
            }
        }
        return this;
    }

    private void checkConfig() {
        if (taskSupplier == null) {
            throw new IllegalStateException("请设置任务检查器");
        }
        if (taskExecutor == null) {
            throw new IllegalStateException("请设置任务执行器");
        }
    }

    private boolean hasTasks() {
        return taskSupplier.get();
    }

    private void executeTask() {
        try {
            taskExecutor.run();
        } catch (Exception e) {
            log.error("[{}] 执行任务异常", schedulerName, e);
        } finally {
            checkAndStopIfNoTasks();
        }
    }

    private void doStartScheduledTask() {
        if (scheduledFuture != null && !scheduledFuture.isDone()) {
            scheduledFuture.cancel(false);
        }

        isRunning.set(true);
        log.info("[{}] 定时任务启动", schedulerName);
        scheduledFuture = scheduler.scheduleWithFixedDelay(
                this::executeTask,
                initialDelay,
                period,
                timeUnit
        );
    }

    private void doStopScheduledTask() {
        if (scheduledFuture != null && !scheduledFuture.isDone()) {
            scheduledFuture.cancel(false);
            scheduledFuture = null;
        }
        isRunning.set(false);
        log.info("[{}] 定时任务停止", schedulerName);
    }

    private void checkAndStopIfNoTasks() {
        synchronized (lock) {
            if (isRunning.get() && !hasTasks()) {
                log.info("[{}] 检测到无任务,停止定时任务", schedulerName);
                doStopScheduledTask();
            }
        }
    }

    /**
     * 通知有任务添加
     */
    public void notifyTaskAdded() {
        synchronized (lock) {
            if (!isRunning.get()) {
                if (isRunning.compareAndSet(false, true)) {
                    log.info("[{}] 收到新任务通知,启动定时任务", schedulerName);
                    doStartScheduledTask();
                }
            }
        }
    }

    /**
     * 通知任务删除
     */
    public void notifyTaskRemoved() {
        checkAndStopIfNoTasks();
    }

    /**
     * 强制停止调度器
     */
    public void stopScheduler() {
        synchronized (lock) {
            doStopScheduledTask();
        }

        if (scheduler != null && !scheduler.isShutdown()) {
            scheduler.shutdownNow();
            try {
                if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                    log.warn("[{}] 调度器关闭超时", schedulerName);
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        log.info("[{}] 调度器已停止", schedulerName);
    }
}