Search code examples
javamultithreadingspring-bootthreadpoolexecutor

Limit the number of threads and wait if max threads reached using @Async annotation


I am using Spring's Java Configuration with AsyncConfigurer:

@Configuration
@EnableAsync
public class AppConfig implements AsyncConfigurer {

@Override
public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(10);
        executor.setThreadNamePrefix("MyExecutor-");
        executor.initialize();
        return executor;
    }
}

Now suppose I have a method with @Async annotation and suppose its called 2 times already and 2 threads are still running. As per my understanding, any new call to it will be added in the queue with capacity 10. Now if I receive 11th Task, what will be the behaviour of it? will it reject the task as stated here: https://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html? or will the caller wait for a queue slot to get empty?

My Requirement is to not exeed a fix number of threads spawned using @Async method and to make the caller wait if max number of threads are reached. Will this be achieved if I use ConcurrentTaskExecutor with a fixed thread pool of a particular size?


Solution

  • I wanted to limit the number of possible threads along with not loosing any message. This requirement of mine was not fulfilled from the existing answers and I found another way to do it. Hence, posting it as an answer:


    I made a Executor Bean as follows:

    @Bean(name = "CustomAsyncExecutor")
    public Executor customThreadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(5);
        executor.setQueueCapacity(0);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.setThreadNamePrefix("Async_Thread_");
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }
    

    And then using

    @Async("CustomAsyncExecutor")
    public void methodName(){
    ....
    }
    

    Given that when the threads are busy & when queue is full, new tasks get rejected,

    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy())

    helped me that when my 5 threads are busy, my invoker thread will execute the task & since my invoker thread is within the async function, it won't take any new tasks. Thus, I won't loose my tasks without increasing the queue size.