简体   繁体   中英

SpringBoot-2.1.3: Parallel Methods Invocation with @Async with CompletableFuture

Below is my code in which am trying to parallelize the 4 methods invocations since each method is independent of each other and performs some memory intensive statistical operations.

@EnableAsync
@Configuration
public class Config {
   
    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();        
        executor.initialize();
        return executor;
    }
}

class OrderStatsService{

    public CumulativeStats compute() {
        log.info("CumulativeResult compute started at " + System.currentTimeMillis() + ", Current Thread Name: " + Thread.currentThread().getName() + ", Current Thread ID: " + Thread.currentThread().getId());
        List<Order> orders = getOrders();// API Call to fetch large set of orders size could be around 100k
        
        CumulativeResult cumulativeResult = new CumulativeResult();

        CompletableFuture<Long> stats1 = getStats1(orders);
        CompletableFuture<List<String>> result2 = getStats2(orders);
        CompletableFuture<Double> result3 = getStats3(orders);
        CompletableFuture<Map<String,String>> result4 = getStats4(orders);

        cumulativeResult.setStats1(stats1);
        cumulativeResult.setStats2(stats2);
        cumulativeResult.setStats3(stats3);
        cumulativeResult.setStats4(stats4);
        return cumulativeResult;
    }
    
    @Async("threadPoolTaskExecutor")
    public CompletableFuture<Long> getStats1(var orders) {
    log.info("getStats1 started at " + System.currentTimeMillis() + ", Current Thread Name: " + Thread.currentThread().getName() + ", Current Thread ID: " + Thread.currentThread().getId());
        //computes some stats
    }

    @Async("threadPoolTaskExecutor")
    public CompletableFuture<List<String>> getStats2(var orders) {
    log.info("getStats2 started at " + System.currentTimeMillis() + ", Current Thread Name: " + Thread.currentThread().getName() + ", Current Thread ID: " + Thread.currentThread().getId());
        //computes some stats
    }

    @Async("threadPoolTaskExecutor")
    public CompletableFuture<Double>  getStats3(var> orders) {
    log.info("getStats3 started at " + System.currentTimeMillis() + ", Current Thread Name: " + Thread.currentThread().getName() + ", Current Thread ID: " + Thread.currentThread().getId());
        //computes some stats
    }

    @Async("threadPoolTaskExecutor")
    public CompletableFuture<Map<String,String>> getStats4(var orders) {
    log.info("getStats4 started at " + System.currentTimeMillis() + ", Current Thread Name: " + Thread.currentThread().getName() + ", Current Thread ID: " + Thread.currentThread().getId());
        //computes some stats
    }

}

I'm getting the expected result but noticed that the main thread which is invoking the compute() is executing the other 4 methods getStats1 , getStats2 , getStats3 , getStats4 methods as well.

 CumulativeResult compute started at 1655783237437, Current Thread Name: http-nio-8080-exec-1, Current Thread ID: 28
 getStats1 started at 1655783238022, Current Thread Name: http-nio-8080-exec-1, Current Thread ID: 28
 getStats2 started at 1655783238024, Current Thread Name: http-nio-8080-exec-1, Current Thread ID: 28
 getStats3 started at 1655783463062, Current Thread Name: http-nio-8080-exec-1, Current Thread ID: 28
 getStats4 started at 1655783238085, Current Thread Name: http-nio-8080-exec-1, Current Thread ID: 28

I thought when we use CompletableFuture for @Async methods with @EnableAsync config those methods would be assigned a new thread for their execution, can someone please explain is this is the expected behavior or not for parallel methods invocation? Is there anything wrong with my config? Or if this is the expected behavior how the parallelism is achieved when we are executing the caller method and the async in same thread?

The required changes can be done in the code.

Step 1: The very first step to executing multithreading is to set up the thread pool config properly.

Here is an example of how you can set it up the size of the thread

@Configuration
@EnableAsync
public class ThreadPoolConfiguration {

@Bean(name = "taskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setThreadNamePrefix("thread-");
    threadPoolTaskExecutor.setCorePoolSize(100);
    threadPoolTaskExecutor.setMaxPoolSize(120);
    threadPoolTaskExecutor.setQueueCapacity(100000);
    threadPoolTaskExecutor.initialize();
    return threadPoolTaskExecutor;
}

Step 2: Using @Async annotation

First, let's go over the rules.

  • @Async annotation - It must be applied to public methods only.
  • Self-invocation for @Async will not work which means calling the async method from within the same class will not work.

For more information on @Async you can go through

Solutions :

  • Keep all the methods with @Async in different service and you can call it from OrderStatsService
  • One more thing you can do is call from the controller and all the @Async methods can be kept in the service class.
  • Mark you annotation with the Bean name like @Async("taskExecutor")

Either of the ways will work for you.

You should set thread pool size for controlling thread count:

final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("CarThread-");

Multi-Threading in Spring Boot Using CompletableFuture

I would recommend checking some facts:

  1. Is your OrderStatsService class static and is it a Bean managed by Spring?
  2. @Async methods of a Bean have to be invoked directly from it's caller. What you are having in your OrderStatsService ( Bean -if you've done this already) is calling the non-@Async method, followed by invoking the @Async methods. In my experience, this won't give you the expected behaviour.

Feel free to correct me if I'm being a dummy dumb dumb.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM