Skip to main content

Design a Distributed / Concurrent Job Scheduler (Quartz / Cron style)

Problem Statement

Design an in-memory concurrent Job Scheduler (similar to the core engine of Quartz Scheduler or Java's ScheduledThreadPoolExecutor). The scheduler must accept task submissions with scheduling parameters (one-time execution after a delay, or periodic recurring executions), execute jobs concurrently using a thread pool, and maintain task schedules in a thread-safe priority structure.

Asked In Companies

Functional Requirements

  • Support scheduling one-time tasks with a specific execution delay.
  • Support scheduling recurring periodic tasks that reschedule themselves after execution.
  • Maintain tasks ordered by their next execution time using a priority structure.
  • Utilize a thread pool to execute scheduled tasks concurrently, ensuring slow-running tasks do not block other jobs.
  • Handle thread safety during task submissions, cancellations, and state evaluations.

Objects Required

  • ScheduledJob (Encapsulation class matching Runnable payload, execution time, and recurrence rules)
  • JobScheduler (Main controller driving priority queues, worker pools, and scheduling threads)

ScheduledJob Class

The ScheduledJob class wraps task logic (a Runnable) and keeps track of execution intervals and next execution times. It implements the Comparable interface to order tasks by execution time.


public class ScheduledJob implements Runnable, Comparable<ScheduledJob> {
    private final String id;
    private final Runnable task;
    private long nextRunTime;
    private final long periodMs;

    public ScheduledJob(String id, Runnable task, long delayMs, long periodMs) {
        this.id = id;
        this.task = task;
        this.nextRunTime = System.currentTimeMillis() + delayMs;
        this.periodMs = periodMs;
    }

    @Override
    public void run() {
        task.run();
    }

    public String getId() { return id; }
    public long getNextRunTime() { return nextRunTime; }
    public long getPeriodMs() { return periodMs; }

    public boolean isRecurring() {
        return periodMs > 0;
    }

    public void updateNextRunTime() {
        if (isRecurring()) {
            this.nextRunTime = System.currentTimeMillis() + periodMs;
        }
    }

    @Override
    public int compareTo(ScheduledJob other) {
        return Long.compare(this.nextRunTime, other.nextRunTime);
    }
}

The constructor calculates the absolute epoch execution time. isRecurring() determines if the task is a one-time or periodic job, while updateNextRunTime() shifts the target window forward for rescheduled execution blocks.


JobScheduler Class

The JobScheduler class manages scheduling. It utilizes a thread-safe PriorityBlockingQueue to track task times and runs a background loop to trigger executions.


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.PriorityBlockingQueue;

public class JobScheduler {
    private final PriorityBlockingQueue<ScheduledJob> taskQueue;
    private final ExecutorService threadPool;
    private final Thread schedulerThread;
    private volatile boolean running;

    public JobScheduler(int poolSize) {
        this.taskQueue = new PriorityBlockingQueue<>();
        this.threadPool = Executors.newFixedThreadPool(poolSize);
        this.schedulerThread = new Thread(this::schedulerLoop);
        this.schedulerThread.setName("Job-Scheduler-Daemon");
        this.running = false;
    }

    public synchronized void start() {
        if (running) return;
        running = true;
        schedulerThread.start();
        System.out.println("Job Scheduler engine started.");
    }

    public synchronized void stop() {
        running = false;
        schedulerThread.interrupt();
        threadPool.shutdown();
        System.out.println("Job Scheduler engine stopped.");
    }

    public void schedule(String id, Runnable task, long delayMs, long periodMs) {
        ScheduledJob job = new ScheduledJob(id, task, delayMs, periodMs);
        taskQueue.add(job);
        // Force the scheduler thread to wake up and re-evaluate queue ordering
        synchronized (taskQueue) {
            taskQueue.notifyAll();
        }
    }

    private void schedulerLoop() {
        try {
            while (running) {
                synchronized (taskQueue) {
                    while (taskQueue.isEmpty() && running) {
                        taskQueue.wait();
                    }
                }

                if (!running) break;

                ScheduledJob nextJob = taskQueue.peek();
                long now = System.currentTimeMillis();

                if (nextJob != null && nextJob.getNextRunTime() <= now) {
                    // Poll tasks safely and submit to execution threads
                    taskQueue.poll();
                    threadPool.submit(() -> {
                        try {
                            nextJob.run();
                        } finally {
                            if (nextJob.isRecurring()) {
                                nextJob.updateNextRunTime();
                                taskQueue.add(nextJob);
                                synchronized (taskQueue) {
                                    taskQueue.notifyAll();
                                }
                            }
                        }
                    });
                } else if (nextJob != null) {
                    // Sleep until the next job is ready to run
                    long sleepTime = nextJob.getNextRunTime() - now;
                    synchronized (taskQueue) {
                        taskQueue.wait(sleepTime);
                    }
                }
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Here is an explanation of the core operations in the JobScheduler class:

  • The constructor configures concurrency pools and initializes a background daemon monitoring thread.
  • start() and stop() manage the lifecycle, shutting down workers and interrupting loops cleanly.
  • schedule() adds a task to the queue and notifies wait blocks to re-evaluate sorting constraints.
  • schedulerLoop() monitors the queue. If empty, it waits for notifications. If a task is ready, it submits the job to the thread pool and reschedules periodic tasks. If no task is ready, it waits for the duration of the delay.

Main Driver Class

This class tests our concurrent job scheduler. It submits one-time and periodic tasks, monitors parallel output logs, and handles clean terminations.


public class Main {
    public static void main(String[] args) throws InterruptedException {
        // Start scheduler with 3 execution threads
        JobScheduler scheduler = new JobScheduler(3);
        scheduler.start();

        System.out.println("Scheduling tasks...");

        // Task A: Run once after 1 second
        scheduler.schedule("TaskA", () -> {
            System.out.println("[" + Thread.currentThread().getName() + "] Task A executed (One-time, expected 1s delay)");
        }, 1000, 0);

        // Task B: Run periodically every 2 seconds, starting immediately
        scheduler.schedule("TaskB", () -> {
            System.out.println("[" + Thread.currentThread().getName() + "] Task B executed (Periodic, runs every 2s)");
        }, 0, 2000);

        // Task C: Run periodically every 3 seconds, starting after a 1 second delay
        scheduler.schedule("TaskC", () -> {
            System.out.println("[" + Thread.currentThread().getName() + "] Task C executed (Periodic, runs every 3s)");
        }, 1000, 3000);

        // Allow execution to run for 7 seconds
        Thread.sleep(7000);

        // Stop the scheduler engine
        System.out.println("\nStopping scheduler...");
        scheduler.stop();
    }
}

The main() driver configures the environment, registers task routines, runs concurrent worker blocks, and triggers safe shutdowns.


Class Diagram

ScheduledJobid: Stringtask: RunnablenextRunTime: longperiodMs: longrun(): voidgetId(): StringgetNextRunTime(): longgetPeriodMs(): longisRecurring(): booleanupdateNextRunTime(): voidcompareTo(other: ScheduledJob): intRunnableJobSchedulertaskQueue: PriorityBlockingQueue<ScheduledJob>threadPool: ExecutorServiceschedulerThread: Threadrunning: booleanstart(): voidstop(): voidschedule(id: String, task: Runnable, delayMs: long, periodMs: long): voidschedulerLoop(): voidMainmain(args: String[]): voidmanages tasks in queue1manydrives

Also See

Comments

Popular posts from this blog

Designing a Parking Lot - Low Level Design

Problem Statement Design a parking lot that can handle vehicles entering and leaving while managing parking across multiple floors. Each vehicle should be assigned a suitable parking spot based on its type, and the spot should be freed once the vehicle exits. The design should also support generating a ticket at entry and optionally calculating the parking fee based on the duration of stay. Asked In Companies Amazon Google Microsoft Uber Walmart Flipkart Meta PayPal Oracle Salesforce Adobe Apple Intuit LinkedIn Atlassian Functional Requirements The design should support multiple vehicle types such as bikes, cars, and trucks A vehicle must be assigned a parking spot compatible with its type A parking spot cannot be assigned to more than one vehicle at a time The parking lot should support multiple levels (floors) The design should search and allocate an availa...

Most Frequently Asked Low Level Design(LLD) Interview Questions

Below are the curated list of most commonly asked Low Level Design (LLD) interview problems. Each problem includes a short description and a link to the complete solution with code and class diagrams. Design Parking Lot System The system should handle parking for different vehicle types such as bikes, cars, and trucks. It should manage slot allocation, availability tracking, and entry/exit flow. The design also ensures efficient usage of parking space under varying load conditions. View Solution Design Elevator / Lift System The system should support multiple elevators operating across floors with request handling logic. It focuses on scheduling algorithms to minimize wait time and optimize movement. It also manages direction control and concurrent floor requests. View Solution Design Movie Ticket Booking System The system should allow users to browse movies, select shows, and book seats. It handles seat ...

Software Design Patterns for LLD Interviews: A Complete Guide

Software Design Patterns for LLD Interviews: A Complete Guide In Software Development Engineer (SDE) interviews—especially for mid-level and senior roles—low-level design (LLD) rounds assess your ability to write clean, reusable, maintainable, and extensible code. The foundation of resolving these architectural challenges lies in the standard Gang of Four (GoF) Design Patterns. Rather than memorizing theoretical definitions, interviewers expect you to apply these patterns to real-world scenarios, identifying the trade-offs of each. Below is a comprehensive guide to the 12 most frequently asked design patterns in LLD interviews, categorized by their classification (Creational, Structural, and Behavioral). Each pattern contains a concrete, real-world Java implementation and a detailed breakdown of design decisions. Creational Design Patterns Creational design patterns deal with object creation mechanisms. They abstract the instantiation process, making a system independent of how...