SpringのAsyncを使用し、ThreadConfigでThreadPoolTaskExecutorを使用してAPIを非同期的に呼び出そうとしています。
@Configuration
@EnableAsync
public class ThreadConfig extends AsyncConfigurerSupport {
@Value("${core.pool.size}")
private int corePoolSize;
@Value("${max.pool.size}")
private int maxPoolSize;
@Value("${queue.capacity}")
private int queueCapacity;
@Override
@Bean
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("default_task_executor_thread");
executor.initialize();
return executor;
}
ここでの設定は次のとおりです。
corePoolSize = 5;
maxPoolSize = 10;
QueueCapacity = 10;
私は次のように非同期サービスを呼び出しています:
for (String path : testList) {
Future<Boolean> pro = services.invokeAPI(path);
}
TestListには約50のレコードがあります。
これを実行すると、コンパイラは10個のスレッドを処理し、invokeAPIメソッドを10回呼び出した後、次のようになります。
org.springframework.core.task.TaskRejectedException: Executor[Java.util.concurrent.ThreadPoolExecutor@3234ad78[Running, pool size = 10, active threads = 10, queued tasks = 10, completed tasks = 0]] did not accept task: org.springframework.aop.interceptor.AsyncExecutionInterceptor$1@5c17b70
例外をスローしてプログラムを終了するのではなく、現在のタスクが完了してスレッドを再割り当てするのを待つと想定していました。
50レコードすべてにinvokeAPIメソッドを呼び出させるにはどうすればよいですか?
編集:testListのレコード数は変更できます。
何か提案がありますか?
これは、プールに使用しているサイズが原因で発生しています。キューのサイズは10であり、使用できる最大スレッド数は10であるため、20タスク(実行中10、キュー内10)の後、エグゼキュータはタスクの拒否を開始します。
この問題を解決するにはさまざまな方法があります。
RejectedExecutionHandler
を提供します。つまり、呼び出し元のスレッドでそれらを実行するか、それらまたは他の何かを破棄します(ユースケースに応じて)。すでにJava CallerRunsPolicy
、AbortPolicy
、DiscardPolicy
、DiscardOldestPolicy
など)によって提供されているものがいくつかあります。指定できます彼らはexecutor#setRejectedExecutionHandler
を使うのが好きです。これはBlockingExecutorの例です
public class BlockingExecutor extends ThreadPoolExecutor {
private final Semaphore semaphore;
public BlockingExecutor(final int corePoolSize, final int poolSize, final int queueSize) {
super(corePoolSize, poolSize, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
semaphore = new Semaphore(poolSize + queueSize);
}
@Override
public void execute(final Runnable task) {
boolean acquired = false;
do {
try {
semaphore.acquire();
acquired = true;
} catch (final InterruptedException e) {
//do something here
}
} while (!acquired);
try {
super.execute(task);
} catch (final RejectedExecutionException e) {
semaphore.release();
throw e;
}
}
protected void afterExecute(final Runnable r, final Throwable t) {
super.afterExecute(r, t);
semaphore.release();
}
}
これにアプローチする1つの方法は、以下のようなものを使用してRejectedExecutionHandlerポリシーを実装することです。
import Java.util.concurrent.RejectedExecutionException;
import Java.util.concurrent.RejectedExecutionHandler;
import Java.util.concurrent.ThreadPoolExecutor;
public class BlockCallerExecutionPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// based on the BlockingQueue documentation below should block until able to place on the queue...
executor.getQueue().put(r);
}
catch (InterruptedException e) {
throw new RejectedExecutionException("Unexpected InterruptedException while waiting to add Runnable to ThreadPoolExecutor queue...", e);
}
}
}
これにより、呼び出し元のスレッド(おそらくメインスレッド)は、ブロッキングキューに空きができるまで待機します。
こんにちは@AkshayChopra、
@Shaizeの応答によると:
import Java.util.concurrent.RejectedExecutionException;
import Java.util.concurrent.RejectedExecutionHandler;
import Java.util.concurrent.ThreadPoolExecutor;
public class RejectedExecutionHandlerImpl implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
executor.getQueue().put(r);
}
catch (InterruptedException e) {
throw new RejectedExecutionException("There was an unexpected InterruptedException whilst waiting to add a Runnable in the executor's blocking queue", e);
}
}
}
taskRejectedExceptionなしでマルチスレッドを使用するには、TaskRejectedHnadlerを実装する必要があります。以下を参照してください。
@Configuration
@EnableAsync
public class ThreadConfig extends AsyncConfigurerSupport {
@Value("${core.pool.size}")
private int corePoolSize;
@Value("${max.pool.size}")
private int maxPoolSize;
@Value("${queue.capacity}")
private int queueCapacity;
@Override
@Bean
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setThreadNamePrefix("default_task_executor_thread");
// add a rejected execution handler
executor.setRejectedExecutionHandler(new RejectedExecutionHandlerImpl());
executor.initialize();
return executor;
}
}
もう1つの方法は、リアクティブ[〜#〜] flux [〜#〜]を非同期ではなく同期メソッドで使用することです。
Flux.just(dtos.toArray()) // in my case an ArrayList
.parallel(corePoolSize) // 8 in my case
.runOn(Schedulers.parallel())
.subscribe(dto -> computeService.compute((CastYourObject) dto));
これで完了です。
ベスト。