Why SpringBoot @EnableScheduling Is Slow and How to Fix It

This article explains why SpringBoot's default @EnableScheduling runs tasks sequentially with noticeable delays, analyzes the underlying single‑threaded scheduler, and provides multiple solutions—including thread‑pool configuration, asynchronous execution, custom executors, and distributed locks—to achieve reliable and scalable scheduled jobs.

Java Backend Technology
Java Backend Technology
Java Backend Technology
Why SpringBoot @EnableScheduling Is Slow and How to Fix It

1. Basic Environment Setup

Include the essential Maven dependencies:

<parent>
    <artifactId>spring-boot-parent</artifactId>
    <groupId>org.springframework.boot</groupId>
    <version>2.7.2</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

and create a main class annotated with @SpringBootApplication to start the application.

2. Problem: Execution Delay and Single‑Threaded Execution

Using @Scheduled(cron = "0/5 * * * * ?") should trigger every five seconds. The expected timestamps are 00:21:10, 00:21:15, 00:21:20, etc., but the actual logs show the task runs at exactly those times only when the job finishes quickly. When the task sleeps for ten seconds, the next execution is delayed by ten seconds, and only one thread (ID 64) processes all runs, causing a backlog.

3. Why the Problem Occurs

The root cause is SpringBoot's TaskSchedulingAutoConfiguration, which creates a ThreadPoolTaskScheduler with a core pool size of 1. Consequently, all scheduled jobs share a single thread, leading to execution delay and blocking when a job takes longer than its interval. The default thread‑pool also uses Integer.MAX_VALUE for max threads and queue size, which is unsafe for production.

4. Solutions

4.1 Modify Configuration File

Adjust the scheduler properties in application.yml or application.properties:

spring:
  task:
    scheduling:
      thread-name-prefix: nzc-schedule-
      pool:
        size: 10

After the change, logs show multiple threads (e.g., nzc‑schedule‑1, nzc‑schedule‑2) handling the job, eliminating the single‑thread bottleneck.

4.2 Asynchronous Execution with a Custom Thread Pool

Define a configuration class that provides a TaskExecutor bean:

@Configuration
public class MyThreadPoolConfig {
    @Bean
    public TaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(200);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("nzc-create-scheduling-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setWaitForTasksToCompleteOnShutdown(true);
        return executor;
    }
}

Inject this executor into the scheduled service and run the job asynchronously with CompletableFuture.runAsync(..., taskExecutor). The logs now display different thread IDs for each execution, confirming parallel processing.

4.3 Async Scheduling with @Async

Add @EnableAsync to the configuration and annotate the scheduled method with @Async("taskExecutor"):

@EnableAsync
@EnableScheduling
@Component
public class ScheduleService {
    @Autowired
    TaskExecutor taskExecutor;

    @Async("taskExecutor")
    @Scheduled(cron = "0/5 * * * * ?")
    public void testSchedule() {
        try {
            Thread.sleep(10000);
            log.info("Current thread ID => {}", Thread.currentThread().getId());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Each execution runs in its own thread (te‑scheduling‑1, te‑scheduling‑2, …), eliminating delay even when the job lasts longer than the interval.

4.4 Distributed Lock for Multi‑Node Scenarios

When multiple instances run the same scheduled job, use Redisson to acquire a distributed lock before execution:

@Configuration
public class MyRedissonConfig {
    @Bean(destroyMethod = "shutdown")
    public RedissonClient redissonClient() throws IOException {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://xxxxx:6379").setPassword("000415");
        return Redisson.create(config);
    }
}

In the scheduled service:

@EnableAsync
@EnableScheduling
@Component
public class ScheduleService {
    @Autowired
    TaskExecutor taskExecutor;
    @Autowired
    RedissonClient redissonClient;
    private final String SCHEDULE_LOCK = "schedule:lock";

    @Async("taskExecutor")
    @Scheduled(cron = "0/5 * * * * ?")
    public void testSchedule() {
        RLock lock = redissonClient.getLock(SCHEDULE_LOCK);
        try {
            lock.lock(10, TimeUnit.SECONDS);
            Thread.sleep(10000);
            log.info("Current thread ID => {}", Thread.currentThread().getId());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

The lock ensures only one node executes the job at a time, preventing duplicate processing in a distributed environment.

5. Summary

SpringBoot's default scheduling uses a single‑threaded pool, which leads to execution delays and blocking for long‑running tasks. By configuring a larger thread pool, leveraging asynchronous execution, or applying a distributed lock with Redisson, developers can achieve reliable, scalable scheduled jobs both in single‑node and multi‑node deployments.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaThreadPoolSchedulingSpringBootAsyncDistributedLock
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.