JVM / Java / Threads / Scheduled Executor

1. ScheduledExecutorService

ScheduledExecutor is a specialized ExecutorService that can: - Run tasks after a delay - Run tasks periodically (fixed rate or fixed delay) Before Java 5, developers used Timer / TimerTask. There was a single thread and one bad task could kill the timer. SchedulerExecutorService fixed all of this. Think of it as an Executor + timer + thread pool.
 
/**
 * =================================
 * Trading Background Jobs
 * ==================================
 * ScheduledExecutorService - Example
 */

package threads.scheduled_executor;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class TradingBackgroundJobs {
    public static void main(String[] args) throws InterruptedException {
        
        ScheduledExecutorService scheduler = 
            Executors.newScheduledThreadPool(
                2,
                new SchedulerThreadFactory()
            );

        // 1. Market data refresh - fixed rate
        scheduler.scheduleAtFixedRate(
            new MarketDataRefresher(),      // command
            0,                              // initial delay
            1,                              // period
            TimeUnit.SECONDS                // unit
        );

        // 2. Risk checks - fixed delay
        scheduler.scheduleWithFixedDelay(
            new RiskCheckTask(),
            0,
            5,
            TimeUnit.SECONDS
        );

        // 3. One-time cleanup after delay
        scheduler.schedule(
            new ExpiredOrderCleanup(), 
            10, 
            TimeUnit.SECONDS
        );

        // Let the system run for a while
        Thread.sleep(20_000);

        System.out.println("Shutting down scheduler ...");
        scheduler.shutdown();

        /*
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-2] Running risk checks ...
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-2] Risk checks completed

            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-2] Running risk checks ...

            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-2] Risk checks completed

            [scheduler-worker-2] Refreshing market price ...
            [scheduler-worker-1] Cleaning up expired orders ...
            [scheduler-worker-2] Refreshing market price ...
            [scheduler-worker-1] Cleanup completed

            [scheduler-worker-2] Refreshing market price ...
            [scheduler-worker-2] Refreshing market price ...
            [scheduler-worker-2] Refreshing market price ...
            [scheduler-worker-1] Running risk checks ...

            [scheduler-worker-2] Refreshing market price ...
            [scheduler-worker-2] Refreshing market price ...
            [scheduler-worker-1] Risk checks completed
            
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-1] Refreshing market price ...
            [scheduler-worker-1] Refreshing market price ...
            Shutting down scheduler ...
            [scheduler-worker-1] Refreshing market price ...
        */
    }
}

class SchedulerThreadFactory implements ThreadFactory {
    
    private final AtomicInteger counter = new AtomicInteger(1);

    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setName("scheduler-worker-" + counter.getAndIncrement());
        return t;
    }
}

/** 
 * ===================================
 * Market Data - Refresher
 * ===================================
 *  - runs at a fixed rate
 *  - exception-safe
 *  - typical low-latency trading task
 */
class MarketDataRefresher implements Runnable {

    @Override
    public void run() {
        try {
            System.out.printf(
                "[%s] Refreshing market price ... %n",
                Thread.currentThread().getName()
            );

            // Simulate market data fetch
            Thread.sleep(300);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            // IMPORTANT: never let exceptions escape
            System.err.println("Market data refresh failed: " + e.getMessage());
        }
    }
}

/**
 * ============================================
 * Risk Check - Task
 * ============================================
 *  - No overlap
 *  - Each run starts after the previous finishes
 *  - Perfect for audits and controls
 */
class RiskCheckTask implements Runnable {

    @Override
    public void run() {
        try {
            System.out.printf(
                "[%s] Running risk checks ... %n",
                Thread.currentThread().getName()
            );

            // Simulate heavy calculations
            Thread.sleep(2000);

            System.out.printf(
                "[%s] Risk checks completed %n",
                Thread.currentThread().getName()
            );

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            System.err.println("Risk check failed: " + e.getMessage());
        }
    }
}

/**
 * Expired Order Clean
 * ==========================
 *  - Runs once
 *  - Delayed startup
 *  - Typical maintenance job
 */
class ExpiredOrderCleanup implements Runnable {

    @Override
    public void run() {
        try {
            System.out.printf(
                "[%s] Cleaning up expired orders ... %n",
                Thread.currentThread().getName()
            );

            // Simulate clean up
            Thread.sleep(1000);

            System.out.printf(
                "[%s] Cleanup completed %n",
                Thread.currentThread().getName()
            );
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            System.err.println("Expired order cleanup failed: " + e.getMessage());
        }
    }
}




References: