404 Not Found

404 Not Found


nginx

Scheduled Tasks and Asynchronous Processing

Scheduled tasks and asynchronous processing are the keys to boosting backend efficiency—schedule what needs to be scheduled and run in parallel what can be run in parallel, so users don't have to wait unnecessarily.

1. What You'll Learn


2. A True Story from a Business Developer

(1) Pain Point: Backlog of Time-Out Orders

Alice discovered a large backlog of unpaid orders in OrderFlow—users were placing orders but not paying, which locked up inventory and prevented other users from making purchases. Charlie reported that there were approximately 2,000 time-out orders per day, collectively locking up inventory worth 50,000 USD. Previously, orders were canceled manually by checking the database every 30 minutes, which was inefficient and prone to oversights.

(2) Solutions for @Scheduled

Spring Boot includes built-in scheduled tasks that can be implemented with a single annotation:

JAVA
@Scheduled(fixedRate = 5 * 60 * 1000)  // Every 5 minutes
public void cancelExpiredOrders() {
    List<Order> expired = orderRepository.findByStatusAndCreatedAtBefore(
        "PENDING", Instant.now().minus(30, ChronoUnit.MINUTES));
    expired.forEach(order -> cancelOrder(order));
    log.info("Cancelled {} expired orders", expired.size());
}

(3) Revenue

After Alice implemented the scheduled task, expired orders are automatically cleared every 5 minutes, inventory is automatically released, and the backlog of expired orders has dropped from 2,000 per day to 0. At the same time, by using @Async to switch notification emails to asynchronous delivery, the response time for the order placement API dropped from 800 ms to 50 ms.


3. @Scheduled Scheduled Tasks

(1) Three Scheduling Methods

Method Parameter Notes Meaning Example
FixedRate fixedRate Fixed interval (counting from the last time) Run every 5 minutes
FixedDelay fixedDelay Fixed delay (from the end of the last session) Wait 5 minutes after the end of the last session
Cron cron Cron expression Runs at 2:00 a.m. every day
100%
gantt
    title FixedRate vs FixedDelay Timing
    dateFormat X
    axisFormat %s

    section FixedRate
    Task A (5min) :a1, 0, 5
    Task B (5min) :a2, 5, 8
    Task C (5min) :a3, 10, 14

    section FixedDelay
    Task A (5min) :b1, 0, 5
    wait 3min     :b2, 5, 8
    Task B (5min) :b3, 8, 13
    wait 3min     :b4, 13, 16
Dimension FixedRate FixedDelay Cron
Execution Interval Fixed (regardless of previous duration) Fixed (after the previous one finishes) Exact Time
Tasks Overlap May Overlap Do Not Overlap May Overlap
Use Cases Polling Data Processing Scheduled Batch Processing

(1) ▶ Example: FixedRate Scheduled Task

JAVA
@Component
@EnableScheduling
public class OrderScheduleTask {

    private final OrderService orderService;
    private static final Logger log = LoggerFactory.getLogger(OrderScheduleTask.class);

    public OrderScheduleTask(OrderService orderService) {
        this.orderService = orderService;
    }

    @Scheduled(fixedRate = 5 * 60 * 1000)  // Every 5 minutes
    public void cancelExpiredOrders() {
        log.info("Starting expired order cleanup...");
        int cancelled = orderService.cancelExpiredOrders(Duration.ofMinutes(30));
        log.info("Cancelled {} expired orders", cancelled);
    }
}

Output:

TEXT
// Execution Successful

(2) ▶ Example: Cron Expressions for Scheduled Tasks

JAVA
@Component
public class DailyReportTask {

    @Scheduled(cron = "0 0 2 * * ?")  // Every day at 2:00 AM
    public void generateDailyReport() {
        // Generate daily order statistics report
    }

    @Scheduled(cron = "0 0 0 1 * ?")  // First day of every month at midnight
    public void generateMonthlyReport() {
        // Generate monthly report
    }
}

Output:

TEXT
// Execution Successful
Cron Field Meaning Valid Values
Seconds 0-59
Min Minutes 0-59
Time Hours 0-23
Day Day of the month 1-31
Month 1-12
Day of the Week Day of the week 0-7 (0/7 = Sun)
Common Cron Expressions Meaning
0 0 2 * * ? Every day at 2 a.m.
0 */5 * * * ? Every 5 minutes
0 0 0 1 * ? Midnight on the 1st of every month
0 0 9-17 * * MON-FRI Weekdays, 9:00 a.m.-5:00 p.m. (on the hour)

4. @Async Asynchronous Processing

(1) Synchronous vs. Asynchronous

(1) ▶ Example: Sending Notifications in Parallel (Blocking)

JAVA
@Service
public class OrderService {

    public Order createOrder(CreateOrderRequest request) {
        Order order = saveOrder(request);
        emailService.sendConfirmation(order);   // Blocks for 500ms
        smsService.sendNotification(order);       // Blocks for 200ms
        return order;  // Total: 700ms wasted
    }
}

Output:

TEXT
// Execution Successful

(2) ▶ Example: Sending Notifications Asynchronously (Non-Blocking)

JAVA
@Service
@EnableAsync
public class NotificationService {

    @Async
    public void sendOrderConfirmation(Order order) {
        // This runs in a separate thread
        // Does not block the caller
        emailService.sendConfirmation(order);
        log.info("Confirmation email sent for order {}", order.getId());
    }

    @Async
    public CompletableFuture<String> sendSmsNotification(Order order) {
        smsService.sendNotification(order);
        return CompletableFuture.completedFuture("SMS sent");
    }
}

// In OrderService
@Service
public class OrderService {

    private final NotificationService notificationService;

    public Order createOrder(CreateOrderRequest request) {
        Order order = saveOrder(request);
        notificationService.sendOrderConfirmation(order);  // Non-blocking
        return order;  // Returns immediately
    }
}

Output:

TEXT
// Execution Successful
Dimension Synchronous Asynchronous
Call Method Block and wait for result Return immediately; execute in the background
Response Time Total Time for All Operations Time for Core Business Operations Only
Error Handling Direct try-catch Handling AsyncUncaughtExceptionHandler
Use Cases Operations That Require Waiting for Results Notifications, Logs, Non-Critical Operations

5. Custom Thread Pool

(1) Issues with the Default Thread Pool

Spring Boot uses SimpleAsyncTaskExecutor by default, which creates a new thread for each task and places no limit on the number of threads, which may result in an OutOfMemoryError.

(1) ▶ Example: Custom ThreadPoolTaskExecutor

JAVA
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("orderflow-async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("Async exception in method {}: {}", method.getName(), ex.getMessage(), ex);
        };
    }
}

Output:

TEXT
// Execution Successful
Parameter Meaning Recommended Value
corePoolSize Number of Core Threads (Resident) Number of CPU Cores
maxPoolSize Maximum number of threads Number of CPU cores x 2
queueCapacity Queue Capacity 100-1,000
threadNamePrefix Thread Name Prefix Project Name-async-
rejectedExecutionHandler Rejection Policy CallerRunsPolicy
Rejection Strategy Behavior Applicable Scenarios
AbortPolicy Throws RejectedExecutionException Default, fast failure
CallerRunsPolicy Executed by the caller's thread Downgraded but not discarded
DiscardPolicy Silent Discard Tolerable Loss
DiscardOldestPolicy Discard the oldest task Prioritize real-time performance

6. Asynchronous Exception Handling

(1) ▶ Example: AsyncUncaughtExceptionHandler

JAVA
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class);

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, objects) -> {
            log.error("Async method [{}] threw exception: {}",
                method.getName(), throwable.getMessage(), throwable);
            // Could also publish to monitoring system
        };
    }
}

Output:

TEXT
// Execution Successful
📌 Key Point: Exceptions thrown by @Async void methods are not propagated to the caller and must be handled using AsyncUncaughtExceptionHandler. Exceptions thrown by methods that return CompletableFuture can be handled using future.exceptionally().


7. Comprehensive Example: OrderFlow Timeout Cancellation + Asynchronous Notification

JAVA
// OrderScheduleTask.java
package com.orderflow.schedule;

import com.orderflow.service.OrderService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Duration;

@Component
public class OrderScheduleTask {

    private static final Logger log = LoggerFactory.getLogger(OrderScheduleTask.class);
    private final OrderService orderService;

    public OrderScheduleTask(OrderService orderService) {
        this.orderService = orderService;
    }

    @Scheduled(fixedRateString = "${orderflow.order.expiry-check-interval:300000}")
    public void cancelExpiredOrders() {
        int timeoutMinutes = 30;
        int cancelled = orderService.cancelExpiredOrders(Duration.ofMinutes(timeoutMinutes));
        if (cancelled > 0) {
            log.info("Cancelled {} orders expired for {} minutes", cancelled, timeoutMinutes);
        }
    }

    @Scheduled(cron = "0 0 2 * * ?")
    public void generateDailyReport() {
        log.info("Generating daily order report...");
        orderService.generateDailyReport();
    }
}

// NotificationService.java
package com.orderflow.service;

import com.orderflow.model.Order;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class NotificationService {

    private static final Logger log = LoggerFactory.getLogger(NotificationService.class);

    @Async
    public void sendOrderConfirmation(Order order) {
        // Simulate email sending
        log.info("Sending confirmation email for order {}", order.getId());
    }

    @Async
    public void sendCancellationNotice(Order order) {
        log.info("Sending cancellation notice for order {}", order.getId());
    }

    @Async
    public CompletableFuture<String> sendSmsNotification(Order order) {
        log.info("Sending SMS for order {}", order.getId());
        return CompletableFuture.completedFuture("SMS sent for order " + order.getId());
    }
}

// AsyncConfig.java
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfig implements AsyncConfigurer {
    private static final Logger log = LoggerFactory.getLogger(AsyncConfig.class);

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor exec = new ThreadPoolTaskExecutor();
        exec.setCorePoolSize(5);
        exec.setMaxPoolSize(20);
        exec.setQueueCapacity(100);
        exec.setThreadNamePrefix("orderflow-async-");
        exec.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        exec.setWaitForTasksToCompleteOnShutdown(true);
        exec.setAwaitTerminationSeconds(60);
        exec.initialize();
        return exec;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) ->
            log.error("Async error in {}: {}", method.getName(), ex.getMessage(), ex);
    }
}

❓ FAQ

Q Can @Scheduled methods only be executed in a single thread?
A By default, yes. Multiple @Scheduled methods share a single-threaded scheduler, so long-running tasks will block other tasks. Solution: Configure the TaskScheduler thread pool, or delegate time-consuming operations to the thread pool using @Async.
Q Does @Async take effect when called within the same class?
A No. Just like @Transactional, @Async relies on AOP proxies, and calls between methods within the same class bypass the proxy. Solutions: 1) Split them into different services; 2) Inject your own proxy.
Q What is the difference between fixedRate and fixedDelay for long-running tasks?
A fixedRate counts down from the start time of the previous task; if a task takes longer than the interval, it will run consecutively or even overlap with other tasks. fixedDelay counts from the end time of the previous task, ensuring a fixed interval between tasks so they do not overlap.
Q How can we prevent scheduled tasks from running multiple times across different instances in a production environment?
A 1) Database row locks (SELECT FOR UPDATE); 2) Distributed locks (Redis/ShedLock); 3) Enable scheduling on only one instance. We recommend the ShedLock library, which is specifically designed to address this issue.
Q How can I dynamically change the time of a scheduled task?
A You can use ScheduledTaskRegistrar to programmatically register tasks, or use the Configuration Center to dynamically refresh them. A simple solution: fixedRateString = "${interval}"—control it via external configuration.
Q What is the difference between an asynchronous method that returns a CompletableFuture and one that returns void?
A A void method is "fire-and-forget"; the caller is not concerned with the result, and exceptions are handled by AsyncUncaughtExceptionHandler. CompletableFuture methods allow for chained handling of results and exceptions, giving the caller more control.

📖 Summary


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Implement a scheduled task for OrderFlow to automatically cancel time-out orders. Every 5 minutes, check for and cancel any PENDING orders that have not been paid for 30 minutes.

  2. Advanced Exercise (Difficulty: ⭐⭐): Modify the order confirmation email and SMS notifications to be sent asynchronously using @Async, customize the thread pool (core=5, max=20), and verify that the response time of the order placement API has been significantly reduced.

  3. Challenge (Difficulty: ⭐⭐⭐): Integrate ShedLock to implement a distributed lock for scheduled tasks, ensuring that when OrderFlow is deployed across multiple instances, only one instance executes a scheduled task at any given time. Consider the lock granularity and timeout design.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏