web-dev-qa-db-ja.com

ThreadPoolTask​​ExecutorのTaskRejectedException

SpringのAsyncを使用し、ThreadConfigでThreadPoolTask​​Executorを使用して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のレコード数は変更できます。

何か提案がありますか?

6
Akshay Chopra

これは、プールに使用しているサイズが原因で発生しています。キューのサイズは10であり、使用できる最大スレッド数は10であるため、20タスク(実行中10、キュー内10)の後、エグゼキュータはタスクの拒否を開始します。

この問題を解決するにはさまざまな方法があります。

  1. 無制限のキューを使用します。つまり、キューのサイズを指定しないでください。したがって、キューはすべてのタスクを保持できます。スレッドが解放されると、タスクが送信されます。
  2. タスクで何かを行うRejectedExecutionHandlerを提供します。つまり、呼び出し元のスレッドでそれらを実行するか、それらまたは他の何かを破棄します(ユースケースに応じて)。すでにJava CallerRunsPolicyAbortPolicyDiscardPolicyDiscardOldestPolicyなど)によって提供されているものがいくつかあります。指定できます彼らはexecutor#setRejectedExecutionHandlerを使うのが好きです。
  3. タスクの余地が増えるまでブロックする独自のBlockingThread Pool Executorを提供します(Semaphoreを使用)。

これは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();
    }
}
3
Sneh

これにアプローチする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);
        }
    }
}

これにより、呼び出し元のスレッド(おそらくメインスレッド)は、ブロッキングキューに空きができるまで待機します。

2
Shaize

こんにちは@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));

これで完了です。

ベスト。

2
Raul Urcan