Skip to main content
  1. Java Concurrency (java.util.concurrent)/

ScheduledThreadPoolExecutor

2 mins

ScheduledThreadPoolExecutor extends ThreadPoolExecutor and implements the ScheduledExecutorService interface. It provides a more flexible way to execute delayed and periodic tasks compared to the legacy Timer class.

Source Code #

View Source on GitHub

Canonical Usage #

When to use: Use ScheduledThreadPoolExecutor whenever you need to execute tasks that should run after a specific delay, or when you need tasks to run periodically (e.g., refreshing a cache every hour, sending a heartbeat signal every 10 seconds).

Common Patterns:

  • Delayed Task Execution: Run a task once after a specific amount of time has passed.
  • Fixed-Rate Scheduling: Task starts every n seconds, regardless of how long the previous task took (useful for tasks that need to stay on a strict schedule).
  • Fixed-Delay Scheduling: Task starts n seconds after the previous task has finished (useful for tasks that shouldn’t overlap).
// One-shot delayed task
scheduledExecutor.schedule(() -> {
    System.out.println("Refreshing cache...");
    cache.refresh();
}, 5, TimeUnit.MINUTES);

// Fixed-rate task (starts every hour)
scheduledExecutor.scheduleAtFixedRate(() -> {
    System.out.println("Sending periodic metrics...");
    metricsService.send();
}, 0, 1, TimeUnit.HOURS);

// Fixed-delay task (starts 10 seconds after previous task ends)
scheduledExecutor.scheduleWithFixedDelay(() -> {
    System.out.println("Scanning for new files...");
    fileScanner.scan();
}, 1, 10, TimeUnit.SECONDS);

Internal Mechanism: DelayedWorkQueue #

The most important internal component of ScheduledThreadPoolExecutor is its specialized queue called DelayedWorkQueue. Unlike a standard BlockingQueue, this is a heap-based priority queue that keeps the next task to be executed at the head.

static class DelayedWorkQueue extends AbstractQueue<Runnable>
    implements BlockingQueue<Runnable> {
    
    // Heap-based priority queue structure
    private RunnableScheduledFuture<?>[] queue =
        new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
    
    // ... leader-follower pattern ...
}

Comparison with Timer #

ScheduledThreadPoolExecutor is superior to Timer for several reasons:

  • Thread Resilience: A single long-running or crashing task in a Timer stops all subsequent tasks.
  • Temporal Flexibility: It can schedule tasks relative to current time or as a fixed-rate period.
  • Resource Management: It supports dynamic pool resizing and task cancellation.