javaspringmultithreadingthreadpoolspring-async

What happens if my thread sleep is greater than the keep alive seconds when the thread pool is full?


I have a spring boot application where a users hits an endpoint and I have to acknowledge that I got their request immediately. I need to do some computation on different thread and send them response on a different endpoint after my computation is over. For executing the task on different thread my thread pool configuration looks something like this:

@Configuration
@EnableAsync
public class SpringAsyncMatchingConfig {

    @Bean(name = "threadTaskExecutor") 
    public TaskExecutor getMatchingTaskExecutor() {  
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();  
        threadPoolTaskExecutor.setCorePoolSize(10);
        threadPoolTaskExecutor.setQueueCapacity(0);
        threadPoolTaskExecutor.setMaxPoolSize(15); 
        return threadPoolTaskExecutor;  
    }  
}

While I do the computation I need to hit one of the endpoint which returns me a token id and does some computation. It usually takes 3 to 4 minutes to do the computation. So what I have done is I have mentioned Thread.sleep(30000) and after 30 seconds is completed I again hit the same api with the token id it provided expecting it to give me a result.

while(result == false) {
    Thread.sleep(30000)
    result = callendpoint(tokenid)
}

Suppose my thread pool is exhausted, it reached its maximum size of 15 threads, and some more tasks are provided to the pool, some of my threads will be in 30 seconds sleep state, will those threads be terminated and assigned a new task because I am in sleep (idle) state? Should I add the set keep alive to prevent this from happening?

threadPoolTaskExecutor.setKeepAliveSeconds(120); 

Is this the right thing to do?


Solution

  • Suppose my thread is exhausted it reached maximum size of 15 and some more tasks are provided to the thread, some of my thread will be 30 seconds sleep state

    This can happen. Such queue is implemented using LinkedBlockingQueue and the behavior is as follows (source Spring Framework 5.3.X reference documentation: 7.4.2. The executor Element):

    1. If the queueCapacity is reached then the executor creates a new thread beyond the corePoolSize up to maxPoolSize.
    2. If the queueCapacity is also reached with maxPoolSize number of threads, the task is rejected.

    The default capacity is unlimited (in fact Integer.MAX_VALUE) which means only corePoolSize number threads will be allocated. If you want to gap the queue capacity, remember to set maxPoolSize a bit higher than corePoolSize. To ensure no task will be rejected, you have to find a suitable balance of the number of core and max threads. Of course all these numbers heavily depend on the expected throughput. With an "unlimited" queue capacity, you don't need to worry about that, however, the performance-tuning is a bit limited as well.


    threadPoolTaskExecutor.setKeepAliveSeconds(120);

    Is this the right thing to do?

    I don't think so. Speaking to the snippet above this one, note that Thread.sleep(30000) with such long time doesn't help you with effective result polling which can be handled thorugh the CompletableFuture instead. Use CompletableFuture::get(long timeout, TimeUnit unit) to stop the thread when a result is not available after 5 minutes. See below what you can do:

    @Component
    public class AsyncClient {
    
        @Async
        public CompletableFuture<Result> fetchResult(String tokenId) {
            Result result = callEndpoint(tokenId);                      // a long call
            return CompletableFuture.completedFuture(results);
        }
    }
    
    @Service
    public class TokenService {
    
        private final AsyncClient asyncClient;
    
        public Result callEndpoint(String tokenId) 
            throws InterruptedException, ExecutionException, TimeoutException 
        {
            CompletableFuture<Result> completableFuture = asyncClient.fetchResult(tokenId);
            return completableFuture.get(5, TimeUnit.SECONDS);
        }
    }
    

    Finally, let me also remind you three basic rules of using @Async: