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.
Timer
The Timer class can schedule tasks but offers few methods, lacks flexibility, and performs poorly under high concurrency. When a
Timeris 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
Timerclass inherits from
Thread, sets the thread name on creation, and runs its
runmethod, which contains a loop that continuously fetches tasks from the queue.
If an exception occurs inside a scheduled task, the entire
Timerstops, which is undesirable. Additionally,
Timerschedules 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,
scheduleAtFixedRatewill start the next execution immediately after the current one finishes, potentially causing overlapping runs.
Example using
scheduleAtFixedRatewhere 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
scheduleWithFixedDelayensures 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
scheduleWithFixedDelayspecifies the initial delay, and the third parameter defines the fixed delay between successive executions.
In summary,
ScheduledExecutorServiceoffers a more robust, flexible, and thread‑safe way to schedule tasks compared to
Timer, especially in high‑concurrency environments.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.