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: 调度器没有启动?
检查点:
- 是否调用了
start()方法 - 初始检查时
taskSupplier是否返回true - 查看日志是否有错误信息
Q2: 任务执行异常会停止调度器吗?
不会。调度器会捕获执行器异常,记录日志后继续运行。
Q3: 如何立即执行任务?
调用 notifyTaskAdded() 方法,调度器会立即启动并执行任务。
Q4: 可以动态修改调度间隔吗?
当前不支持。需要停止后重新创建调度器。
Q5: 支持集群环境吗?
需要额外处理。当前设计是单机版,集群环境需要:
- 分布式锁控制任务检查
- 共享的任务存储(如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);
}
}