Backend Development 8 min read

Mastering Java Scheduled Tasks: Timer vs ScheduledExecutorService

This article compares Java's Timer and ScheduledExecutorService for scheduling tasks, explains their internal workings, demonstrates common pitfalls, and provides multiple code examples showing how to schedule one‑off, fixed‑rate, and fixed‑delay tasks using both Runnable and Callable.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Java Scheduled Tasks: Timer vs ScheduledExecutorService

Timer

The Timer class can schedule tasks but offers few methods, lacks flexibility, and performs poorly under high concurrency. When a

Timer

is created, it starts a dedicated thread that repeatedly pulls tasks from a queue and executes them.

Example of two periodic tasks that print the current thread name every 2 seconds:

<code>public class TimerDemo {
    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }, new Date(), 2000);
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ", task...");
            }
        }, new Date(), 2000);
    }
}
</code>

Both tasks run on the same thread, as shown in the console output. The

Timer

class inherits from

Thread

, sets the thread name on creation, and runs its

run

method, which contains a loop that continuously fetches tasks from the queue.

If an exception occurs inside a scheduled task, the entire

Timer

stops, which is undesirable. Additionally,

Timer

schedules based on absolute time, making it sensitive to system clock changes.

ScheduledExecutorService

The ScheduledExecutorService interface combines task scheduling with thread‑pool capabilities. Its primary implementation is

ScheduledThreadPoolExecutor

, which extends

ThreadPoolExecutor

. This design allows flexible, high‑performance scheduling of both one‑off and periodic tasks.

Scheduling a delayed task using

Callable

:

<code>public class ScheduledExecutorServiceDemo {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService sch = new ScheduledThreadPoolExecutor(5);
        ScheduledFuture<String> f1 = sch.schedule(() -> {
            System.out.println(Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
            return "Task1";
        }, 5, TimeUnit.SECONDS);
        ScheduledFuture<String> f2 = sch.schedule(() -> {
            System.out.println(Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
            return "Task2";
        }, 6, TimeUnit.SECONDS);
        System.out.println(f1.get());
        System.out.println(f2.get());
    }
}
</code>

The two tasks start at nearly the same moment; the second begins one second later because its delay is 6 seconds versus 5 seconds for the first.

Scheduling a delayed task using

Runnable

:

<code>public class ScheduledExecutorServiceDemo2 {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService sch = new ScheduledThreadPoolExecutor(5);
        sch.schedule(() -> {
            System.out.println("A起始时间: " + Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("A结束时间: " + Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
        }, 2, TimeUnit.SECONDS);
        sch.schedule(() -> {
            System.out.println("B起始时间: " + Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
            try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("B结束时间: " + Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
        }, 2, TimeUnit.SECONDS);
    }
}
</code>

When a task’s execution time exceeds its period,

scheduleAtFixedRate

will start the next execution immediately after the current one finishes, potentially causing overlapping runs.

Example using

scheduleAtFixedRate

where the task runs longer than the period:

<code>public class ScheduledExecutorServiceDemo3 {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService sch = new ScheduledThreadPoolExecutor(5);
        sch.scheduleAtFixedRate(() -> {
            System.out.println("A起始时间: " + Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("A结束时间: " + Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
        }, 1, 2, TimeUnit.SECONDS);
    }
}
</code>

Using

scheduleWithFixedDelay

ensures a fixed pause between the end of one execution and the start of the next:

<code>public class ScheduledExecutorServiceDemo4 {
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService sch = new ScheduledThreadPoolExecutor(5);
        sch.scheduleWithFixedDelay(() -> {
            System.out.println("A起始时间: " + Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println("A结束时间: " + Thread.currentThread().getName() + " 当前时间: " + System.currentTimeMillis());
        }, 1, 2, TimeUnit.SECONDS);
    }
}
</code>

The second parameter of

scheduleWithFixedDelay

specifies the initial delay, and the third parameter defines the fixed delay between successive executions.

In summary,

ScheduledExecutorService

offers a more robust, flexible, and thread‑safe way to schedule tasks compared to

Timer

, especially in high‑concurrency environments.

JavaConcurrencythreadpoolTimerscheduled tasksScheduledExecutorService
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.