IceMan
IceMan

Reputation: 123

SimpleAsyncTaskExecutor fires up only 8 threads

I am trying to understand the behavior of @Async in Spring Boot, by using the default SimpleAsyncTaskExecutor (where I don't explicitly define any Executor bean). According to the documentation of SimpleAsyncTaskExecutor, 'By default, the number of concurrent threads is unlimited'. But on running the sample code below, all I can see is that only 8 threads are fired up, and the remaining tasks are waiting to get a new thread to execute them. I know this can be prevented with a custom Executor where I can define the size of the thread pool. But I want to know if my understanding of SimpleAsyncTaskExecutor is correct or not, or something is not right with my code.

Main class

@SpringBootApplication
@EnableAsync
public class MainRunner {

    private static final Logger LOGGER = LoggerFactory.getLogger(MainRunner.class);

    public static void main(String[] args) {
        ApplicationContext applicationContext = SpringApplication.run(MainRunner.class);
        MyService myService = (MyService) applicationContext.getBean("myService");
        LOGGER.info("Starting the submission of tasks...");
        for (int i = 1; i <= 50; i++) {
            myService.doSomething("Number" + i);
        }
        LOGGER.info("Finished submission of tasks...");

    }
}

MyService class

@Service
public class MyService {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyService.class);

    @Async
    public void doSomething(String userName) {
        LOGGER.info(Thread.currentThread().getName() + ", "
                + Thread.currentThread().getId() + ", NAME: " + userName + " STARTING...");
        for (int i = 0; i < 10000; i++) {
            for (int j = 0; j < 1000000; j++) {
                int res = i + j;
            }
        }
        LOGGER.info(Thread.currentThread().getName() + ", "
                + Thread.currentThread().getId() + ", NAME: " + userName + " COMPLETE...");
    }
}

I expect to see that all 50 tasks are started, and they don't wait for a thread ready to process them. But the above code causes first 8 tasks submitted to start off, and the remaining tasks are waiting for the running tasks to complete in order to be picked up and executed.

2019-09-19 09:33:06.560  INFO 17376 --- [           main] sample.MainRunner                        : Starting the submission of tasks...
2019-09-19 09:33:06.564  INFO 17376 --- [           main] sample.MainRunner                        : Finished submission of tasks...
2019-09-19 09:33:06.566  INFO 17376 --- [         task-8] sample.MyService                         : task-8, 45, NAME: Number8 STARTING...
2019-09-19 09:33:06.566  INFO 17376 --- [         task-1] sample.MyService                         : task-1, 38, NAME: Number1 STARTING...
2019-09-19 09:33:06.566  INFO 17376 --- [         task-7] sample.MyService                         : task-7, 44, NAME: Number7 STARTING...
2019-09-19 09:33:06.567  INFO 17376 --- [         task-4] sample.MyService                         : task-4, 41, NAME: Number4 STARTING...
2019-09-19 09:33:06.566  INFO 17376 --- [         task-6] sample.MyService                         : task-6, 43, NAME: Number6 STARTING...
2019-09-19 09:33:06.567  INFO 17376 --- [         task-2] sample.MyService                         : task-2, 39, NAME: Number2 STARTING...
2019-09-19 09:33:06.567  INFO 17376 --- [         task-5] sample.MyService                         : task-5, 42, NAME: Number5 STARTING...
2019-09-19 09:33:06.567  INFO 17376 --- [         task-3] sample.MyService                         : task-3, 40, NAME: Number3 STARTING...

It waits for the first 8 to complete, and then the remaining tasks are executed. Is my understanding of SimpleAsyncTaskExecutor wrong here?

Upvotes: 4

Views: 6409

Answers (3)

Ahmed Nabil
Ahmed Nabil

Reputation: 19036

When there is no custom Async TaskExecution configuration defined, Springboot will use the default one.
TaskExecutionAutoConfiguration with the bean name applicationTaskExecutor.

You can find the following log line during Springboot startup:

INFO  org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.initialize - Initializing ExecutorService 'applicationTaskExecutor'

During build this default TaskExecution configuration, Springboot will use TaskExecutionProperties which contains the default configs values.

Inside it, we can see the default used coreSize which is 8

private int coreSize = 8;

Of course, we can override the default Async TaskExecution configuration and/or create multiple configs.

@Configuration
@EnableAsync
public class EnableAsyncConfig implements AsyncConfigurer {

    @Bean
    public Executor taskExecutor() {
        // Async thread pool configuration
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(40);
        executor.initialize();
        return executor;
    }

}

Upvotes: 5

Jason Warner
Jason Warner

Reputation: 2723

Your code is not using the SimpleAsyncTaskExecutor.

Using @EnableAsync simply

Enables Spring's asynchronous method execution capability, similar to functionality found in Spring's XML namespace.

Spring does not create a SimpleAsyncTaskExecutor based on that annotation. Looking at the log output:

2019-09-19 12:45:43.475 INFO 19660 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'

It seems that Spring is creating it appears a default ThreadPoolTaskExecutor, which is probably tied the number of cores on your machine (I didn't really check though).

If you really want the SimpleAsyncTaskExecutor you can implement the AsyncConfigurer interface in your configuration

@SpringBootApplication
@EnableAsync
public class MainRunner implements AsyncConfigurer {

  private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(MainRunner.class);

  @Override
  public Executor getAsyncExecutor() {
      return new SimpleAsyncTaskExecutor();
  }

  public static void main(String[] args) {
    ApplicationContext applicationContext = SpringApplication.run(MainRunner.class);
    MyService myService = (MyService) applicationContext.getBean("myService");
    LOGGER.info("Starting the submission of tasks...");
    for (int i = 1; i <= 50; i++)
    {
      myService.doSomething("Number" + i);
    }
    LOGGER.info("Finished submission of tasks...");
  }
}

Upvotes: 5

Ryuzaki L
Ryuzaki L

Reputation: 40078

Yes threads are limited by available cores in CPU here

Core

A core is usually the basic computation unit of the CPU - it can run a single program context (or multiple ones if it supports hardware threads such as hyperthreading on Intel CPUs)

CPU

A CPU may have one or more cores to perform tasks at a given time. These tasks are usually software processes and threads that the OS schedules. Note that the OS may have many threads to run, but the CPU can only run X such tasks at a given time, where X = number cores * number of hardware threads per core. The rest would have to wait for the OS to schedule them whether by preempting currently running tasks or any other means.

What will happen if you have more number of threads ?

Suppose if you have X number of threads then CPU scheduler is giving each one of those X threads some share of CPU time. Some threads will be running in parallel (if you have 4 cores, then 4 threads will be running in parallel at any one time or if you have 4 core hyperthreading on Intel CPUs then total 8 threads will be running parallel) and remaining threads will be in waiting or running concurrently.You can use this command to find number of available processors Runtime.getRuntime().availableProcessors()

Upvotes: 1

Related Questions