Backend Development 18 min read

Various Implementations of Delayed Task Scheduling in Java: Quartz, DelayQueue, HashedWheelTimer, Redis, and RabbitMQ

This article compares delayed tasks with scheduled tasks and presents five practical Java implementations—database polling with Quartz, JDK DelayQueue, Netty's HashedWheelTimer, Redis sorted sets and keyspace notifications, and RabbitMQ delayed queues—detailing their code, advantages, and drawbacks.

Top Architect
Top Architect
Top Architect
Various Implementations of Delayed Task Scheduling in Java: Quartz, DelayQueue, HashedWheelTimer, Redis, and RabbitMQ

In development, delayed tasks such as automatically canceling unpaid orders after a certain time are common, and they differ from scheduled tasks in trigger time, execution cycle, and granularity. The article explores four solution patterns and a fifth using message queues.

1. Database Polling (Quartz)

For small projects, a thread periodically scans the database for overdue orders and updates them. The Maven dependency and a sample MyJob class are shown.

<dependency>
  <groupId>org.quartz-scheduler</groupId>
  <artifactId>quartz</artifactId>
  <version>2.2.2</version>
</dependency>
package com.rjzheng.delay1;

import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

public class MyJob implements Job {
    public void execute(JobExecutionContext context) throws JobExecutionException {
        System.out.println("要去数据库扫描啦。。。");
    }
    public static void main(String[] args) throws Exception {
        JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
                .withIdentity("job1", "group1").build();
        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group3")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(3).repeatForever())
                .build();
        Scheduler scheduler = new StdSchedulerFactory().getScheduler();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();
    }
}

Pros: simple, cluster‑friendly. Cons: high memory usage, latency up to the scan interval, heavy DB load for large data sets.

2. JDK DelayQueue

Uses the built‑in DelayQueue which only releases elements after their delay expires. An OrderDelay class implements Delayed .

package com.rjzheng.delay2;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class OrderDelay implements Delayed {
    private String orderId;
    private long timeout;
    OrderDelay(String orderId, long timeout) {
        this.orderId = orderId;
        this.timeout = timeout + System.nanoTime();
    }
    public int compareTo(Delayed other) { /* omitted for brevity */ }
    public long getDelay(TimeUnit unit) {
        return unit.convert(timeout - System.nanoTime(), TimeUnit.NANOSECONDS);
    }
    void print() { System.out.println(orderId + "编号的订单要删除啦。。。。"); }
}

Demo code shows three‑second delayed removal of orders. Pros: high efficiency, low latency. Cons: data loss on server restart, difficult cluster scaling, possible OOM for massive queues, higher code complexity.

3. Time Wheel (HashedWheelTimer)

Netty's HashedWheelTimer provides a circular timing wheel. Maven dependency and a test class are provided.

<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-all</artifactId>
  <version>4.1.24.Final</version>
</dependency>
package com.rjzheng.delay3;

import io.netty.util.*;
import java.util.concurrent.TimeUnit;

public class HashedWheelTimerTest {
    static class MyTimerTask implements TimerTask {
        boolean flag;
        MyTimerTask(boolean flag) { this.flag = flag; }
        public void run(Timeout timeout) {
            System.out.println("要去数据库删除订单了。。。。");
            this.flag = false;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyTimerTask timerTask = new MyTimerTask(true);
        Timer timer = new HashedWheelTimer();
        timer.newTimeout(timerTask, 5, TimeUnit.SECONDS);
        int i = 1;
        while (timerTask.flag) {
            Thread.sleep(1000);
            System.out.println(i + "秒过去了");
            i++;
        }
    }
}

Pros: efficient, lower latency than DelayQueue, simpler code. Cons: same restart‑loss issue, cluster expansion is non‑trivial, memory limits can cause OOM.

4. Redis Based Solutions

Two approaches using Redis sorted sets (zset) and keyspace notifications. The first stores order IDs with timestamps as scores and polls them; the second relies on EXPIRE events.

package com.rjzheng.delay4;

import redis.clients.jedis.*;
import java.util.*;

public class AppTest {
    private static final String ADDR = "127.0.0.1";
    private static final int PORT = 6379;
    private static JedisPool jedisPool = new JedisPool(ADDR, PORT);
    public static Jedis getJedis() { return jedisPool.getResource(); }
    public void productionDelayMessage() {
        for (int i = 0; i < 5; i++) {
            Calendar cal = Calendar.getInstance();
            cal.add(Calendar.SECOND, 3);
            int ts = (int)(cal.getTimeInMillis() / 1000);
            getJedis().zadd("OrderId", ts, "OID000000" + i);
            System.out.println(System.currentTimeMillis() + "ms:redis生成了一个订单任务:订单ID为" + "OID000000" + i);
        }
    }
    public void consumerDelayMessage() {
        Jedis jedis = getJedis();
        while (true) {
            Set
items = jedis.zrangeWithScores("OrderId", 0, 0);
            if (items == null || items.isEmpty()) {
                try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                continue;
            }
            int score = (int)((Tuple)items.toArray()[0]).getScore();
            int now = (int)(Calendar.getInstance().getTimeInMillis() / 1000);
            if (now >= score) {
                String orderId = ((Tuple)items.toArray()[0]).getElement();
                Long removed = jedis.zrem("OrderId", orderId);
                if (removed != null && removed > 0) {
                    System.out.println(System.currentTimeMillis() + "ms:redis消费了一个任务:消费的订单OrderId为" + orderId);
                }
            }
        }
    }
    public static void main(String[] args) {
        AppTest app = new AppTest();
        app.productionDelayMessage();
        app.consumerDelayMessage();
    }
}

Keyspace notification version adds notify-keyspace-events Ex to redis.conf and subscribes to __keyevent@0__:expired to react when a key expires.

package com.rjzheng.delay5;

import redis.clients.jedis.*;

public class RedisTest {
    private static final String ADDR = "127.0.0.1";
    private static final int PORT = 6379;
    private static JedisPool jedis = new JedisPool(ADDR, PORT);
    private static RedisSub sub = new RedisSub();
    public static void init() {
        new Thread(() -> jedis.getResource().subscribe(sub, "__keyevent@0__:expired")).start();
    }
    public static void main(String[] args) throws InterruptedException {
        init();
        for (int i = 0; i < 10; i++) {
            String orderId = "OID000000" + i;
            jedis.getResource().setex(orderId, 3, orderId);
            System.out.println(System.currentTimeMillis() + "ms:" + orderId + "订单生成");
        }
    }
    static class RedisSub extends JedisPubSub {
        @Override
        public void onMessage(String channel, String message) {
            System.out.println(System.currentTimeMillis() + "ms:" + message + "订单取消");
        }
    }
}

Pros: high accuracy, persistence in Redis, easy cluster scaling. Cons: requires Redis maintenance, Pub/Sub is fire‑and‑forget (possible message loss on disconnect).

5. RabbitMQ Delayed Queue

RabbitMQ can implement delayed messages using x-message-tt for TTL and dead‑letter exchange routing. The article mentions the approach but omits full code due to length.

Pros: efficient, supports distributed deployment, messages are persisted for reliability. Cons: adds operational complexity and cost due to RabbitMQ management.

Overall, each method has trade‑offs in reliability, latency, resource consumption, and scalability, and the choice depends on the specific requirements of the system.

JavaredisSchedulingRabbitMQdelayed tasksQuartzDelayQueue
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.