Backend Development 16 min read

Implementing Scheduled Tasks in Java: Timer, ScheduledExecutorService, Spring Task, and Distributed Approaches

This article explains several ways to implement scheduled tasks in Java, covering the simple Timer class, the more robust ScheduledExecutorService, Spring's @Scheduled annotation, and distributed solutions using Redis ZSet and key‑space notifications, with code examples and practical considerations.

Full-Stack Internet Architecture
Full-Stack Internet Architecture
Full-Stack Internet Architecture
Implementing Scheduled Tasks in Java: Timer, ScheduledExecutorService, Spring Task, and Distributed Approaches

Scheduled tasks are common in real‑world development, such as automatically canceling unpaid orders after 30 minutes or performing nightly data aggregation. This article reviews the simplest implementations and their trade‑offs.

TOP 1: Timer

Timer is a JDK‑provided class that can be used directly in any project. Below is a basic implementation.

public class MyTimerTask {
    public static void main(String[] args) {
        // Define a task
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask:" + new Date());
            }
        };
        // Timer instance
        Timer timer = new Timer();
        // Schedule task (delay 1s, repeat every 3s)
        timer.schedule(timerTask, 1000, 3000);
    }
}

Running the program produces output similar to:

Run timerTask:Mon Aug 17 21:29:25 CST 2020 Run timerTask:Mon Aug 17 21:29:28 CST 2020 Run timerTask:Mon Aug 17 21:29:31 CST 2020

Timer drawbacks

When a task runs longer than its scheduled interval, it blocks other tasks, and an exception in one task terminates the whole timer thread.

Issue 1: Long‑running task delays others

The following code demonstrates two tasks where the first sleeps for 5 seconds.

public class MyTimerTask {
    public static void main(String[] args) {
        // Task 1
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("进入 timerTask 1:" + new Date());
                try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("Run timerTask 1:" + new Date());
            }
        };
        // Task 2
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask 2:" + new Date());
            }
        };
        Timer timer = new Timer();
        timer.schedule(timerTask, 1000, 3000);
        timer.schedule(timerTask2, 1000, 3000);
    }
}

Because task 1 takes 5 seconds, task 2 is also delayed, stretching its interval from 3 seconds to 10 seconds.

Issue 2: Exception in a task stops others

If a task throws an exception, the timer thread terminates and no further tasks run.

public class MyTimerTask {
    public static void main(String[] args) {
        TimerTask timerTask = new TimerTask() {
            @Override
            public void run() {
                System.out.println("进入 timerTask 1:" + new Date());
                int num = 8 / 0; // simulate exception
                System.out.println("Run timerTask 1:" + new Date());
            }
        };
        TimerTask timerTask2 = new TimerTask() {
            @Override
            public void run() {
                System.out.println("Run timerTask 2:" + new Date());
            }
        };
        Timer timer = new Timer();
        timer.schedule(timerTask, 1000, 3000);
        timer.schedule(timerTask2, 1000, 3000);
    }
}

The exception aborts the timer thread, preventing task 2 from executing.

Timer summary

Timer is convenient but unsuitable for production when tasks may run long or throw exceptions.

TOP 2: ScheduledExecutorService

Introduced in JDK 1.5, ScheduledExecutorService provides all Timer features and solves its problems.

public class MyScheduledExecutorService {
    public static void main(String[] args) {
        // Create a thread pool with 10 threads
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        // Execute a task every 3 seconds after an initial 1‑second delay
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Run Schedule:" + new Date());
        }, 1, 3, TimeUnit.SECONDS);
    }
}

Output:

Run Schedule:Mon Aug 17 21:44:23 CST 2020 Run Schedule:Mon Aug 17 21:44:26 CST 2020 Run Schedule:Mon Aug 17 21:44:29 CST 2020

Reliability tests

1️⃣ Long‑running task

Two tasks are scheduled; the first sleeps for 5 seconds. The second continues to run on schedule, showing no interference.

public class MyScheduledExecutorService {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        // Task 1 (long running)
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("进入 Schedule:" + new Date());
            try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("Run Schedule:" + new Date());
        }, 1, 3, TimeUnit.SECONDS);
        // Task 2 (normal)
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Run Schedule2:" + new Date());
        }, 1, 3, TimeUnit.SECONDS);
    }
}

The results confirm that task 2 is unaffected by the delay of task 1.

2️⃣ Exception handling

When task 1 throws an exception, task 2 still runs normally.

public class MyScheduledExecutorService {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        // Task 1 (throws exception)
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("进入 Schedule:" + new Date());
            int num = 8 / 0; // exception
            System.out.println("Run Schedule:" + new Date());
        }, 1, 3, TimeUnit.SECONDS);
        // Task 2
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("Run Schedule2:" + new Date());
        }, 1, 3, TimeUnit.SECONDS);
    }
}

Task 2 continues to execute despite the exception in task 1.

ScheduledExecutorService summary

For single‑machine production environments, ScheduledExecutorService is the recommended choice because it isolates tasks from each other's runtime issues.

TOP 3: Spring Task

When using Spring or Spring Boot, you can leverage the framework’s built‑in scheduling support, which also allows cron‑style expressions for complex schedules such as “every Friday at 23:59:59”.

1️⃣ Enable scheduling

@SpringBootApplication
@EnableScheduling // enable scheduling
public class DemoApplication {
    // ...
}

2️⃣ Add a scheduled method

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class TaskUtils {
    @Scheduled(cron = "59 59 23 0 0 5") // every Friday 23:59:59
    public void doTask() {
        System.out.println("我是定时任务~");
    }
}

The task runs automatically after the application starts.

Cron expression basics

A cron expression consists of 6 (or 7) fields separated by spaces. The article includes two illustrative images and a link to an online generator.

Knowledge extension: Distributed scheduled tasks

For distributed environments, Redis can be used. Two common patterns are presented.

① ZSet implementation

Tasks are stored in a sorted set with the execution timestamp as the score. A loop polls the set each second and processes due tasks.

import redis.clients.jedis.Jedis;
import utils.JedisUtils;
import java.time.Instant;
import java.util.Set;

public class DelayQueueExample {
    private static final String _KEY = "myTaskQueue";
    public static void main(String[] args) throws InterruptedException {
        Jedis jedis = JedisUtils.getJedis();
        long delayTime = Instant.now().plusSeconds(30).getEpochSecond();
        jedis.zadd(_KEY, delayTime, "order_1");
        // add more test data ...
        doDelayQueue(jedis);
    }
    public static void doDelayQueue(Jedis jedis) throws InterruptedException {
        while (true) {
            Instant nowInstant = Instant.now();
            long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond();
            long nowSecond = nowInstant.getEpochSecond();
            Set
data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);
            for (String item : data) {
                System.out.println("消费:" + item);
            }
            jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);
            Thread.sleep(1000);
        }
    }
}

② Key‑space notifications

Enable Redis key‑space events (config set notify-keyspace-events Ex) and subscribe to expiration messages to trigger tasks.

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import utils.JedisUtils;

public class TaskExample {
    public static final String _TOPIC = "__keyevent@0__:expired";
    public static void main(String[] args) {
        Jedis jedis = JedisUtils.getJedis();
        doTask(jedis);
    }
    public static void doTask(Jedis jedis) {
        jedis.psubscribe(new JedisPubSub() {
            @Override
            public void onPMessage(String pattern, String channel, String message) {
                System.out.println("收到消息:" + message);
            }
        }, _TOPIC);
    }
}

The article concludes with a set of promotional links and a QR code for the author’s contact.

Javadistributed schedulingRedisSpringTimerscheduled tasksScheduledExecutorService
Full-Stack Internet Architecture
Written by

Full-Stack Internet Architecture

Introducing full-stack Internet architecture technologies centered on Java

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.