Java: ExecutorService, который блокируется при представлении после определенного размера очереди

Я пытаюсь закодировать решение, в котором один stream создает задачи интенсивного ввода-вывода, которые могут выполняться параллельно. Каждая задача имеет значительные данные в памяти. Поэтому я хочу ограничить количество задач, которые ждут момента.

Если я создам ThreadPoolExecutor следующим образом:

ThreadPoolExecutor executor = new ThreadPoolExecutor(numWorkerThreads, numWorkerThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(maxQueue)); 

Затем executor.submit(callable) throws RejectedExecutionException когда очередь заполняется, и все streamи уже заняты.

Что я могу сделать, чтобы сделать блок executor.submit(callable) когда очередь заполнена, и все streamи заняты?

EDIT : Я пробовал:

 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 

И это несколько улучшает эффект, который я хочу достичь, но неэлегантным способом (в вызывающем streamе запускаются в основном отклоненные streamи, поэтому это блокирует вызывающий stream от отправки больше).

EDIT: (через 5 лет после вопроса)

Чтобы кто-либо читал этот вопрос и его ответы, пожалуйста, не принимайте принятый ответ как одно правильное решение. Прочитайте все ответы и комментарии.

Я сделал то же самое. Хитрость заключается в создании BlockingQueue, где метод offer () действительно является put (). (вы можете использовать любую базу BlockingQueue, которую вы хотите).

 public class LimitedQueue extends LinkedBlockingQueue { public LimitedQueue(int maxSize) { super(maxSize); } @Override public boolean offer(E e) { // turn offer() and add() into a blocking calls (unless interrupted) try { put(e); return true; } catch(InterruptedException ie) { Thread.currentThread().interrupt(); } return false; } } 

Обратите внимание, что это работает только для пула streamов, где corePoolSize==maxPoolSize поэтому будьте осторожны (см. Комментарии).

Вот как я решил это на моем конце:

(обратите внимание: это решение блокирует stream, который отправляет Callable, поэтому он предотвращает выброс RejectedExecutionException)

 public class BoundedExecutor extends ThreadPoolExecutor{ private final Semaphore semaphore; public BoundedExecutor(int bound) { super(bound, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); semaphore = new Semaphore(bound); } /**Submits task to execution pool, but blocks while number of running threads * has reached the bound limit */ public  Future submitButBlockIfFull(final Callable task) throws InterruptedException{ semaphore.acquire(); return submit(task); } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); semaphore.release(); } } 

В принятом в настоящее время ответе есть потенциально значимая проблема – он изменяет поведение ThreadPoolExecutor.execute таким образом, что если у вас есть corePoolSize < maxPoolSize , логика ThreadPoolExecutor никогда не добавит дополнительных сотрудников за пределы ядра.

Из ThreadPoolExecutor .execute (Runnable):

  if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command); 

В частности, этот последний «блок» никогда не будет удален.

Лучшая альтернатива - сделать что-то похожее на то, что OP уже делает - использовать RejectedExecutionHandler для выполнения одной и той же логики:

 public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { try { if (!executor.isShutdown()) { executor.getQueue().put(r); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RejectedExecutionException("Executor was interrupted while the task was waiting to put on work queue", e); } } 

Есть некоторые вещи, которые следует учитывать при таком подходе, как указано в комментариях (ссылаясь на этот ответ ):

  1. Если corePoolSize==0 , то есть условие гонки, в котором все streamи в пуле могут умереть до того, как задача будет видна
  2. Использование реализации, которая обертывает задачи очереди (не относится к ThreadPoolExecutor ), приведет к проблемам, если обработчик также не обернет ее таким же образом.

Сохраняя эти соображения, это решение будет работать для большинства типичных ThreadPoolExecutors и будет правильно обрабатывать случай, когда corePoolSize < maxPoolSize .

Я думаю, что это так же просто, как использование ArrayBlockingQueue вместо aa LinkedBlockingQueue .

Игнорируй меня … это абсолютно неправильно. ThreadPoolExecutor вызывает Queue#offer не put которое потребует эффекта.

Вы можете расширить ThreadPoolExecutor и предоставить реализацию execute(Runnable) которая вызывает put вместо offer .

Боюсь, это не кажется вполне удовлетворительным ответом.

У меня была аналогичная проблема, и я реализовал это, используя beforeExecute/afterExecute от ThreadPoolExecutor :

 import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * Blocks current task execution if there is not enough resources for it. * Maximum task count usage controlled by maxTaskCount property. */ public class BlockingThreadPoolExecutor extends ThreadPoolExecutor { private final ReentrantLock taskLock = new ReentrantLock(); private final Condition unpaused = taskLock.newCondition(); private final int maxTaskCount; private volatile int currentTaskCount; public BlockingThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, int maxTaskCount) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); this.maxTaskCount = maxTaskCount; } /** * Executes task if there is enough system resources for it. Otherwise * waits. */ @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); taskLock.lock(); try { // Spin while we will not have enough capacity for this job while (maxTaskCount < currentTaskCount) { try { unpaused.await(); } catch (InterruptedException e) { t.interrupt(); } } currentTaskCount++; } finally { taskLock.unlock(); } } /** * Signalling that one more task is welcome */ @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); taskLock.lock(); try { currentTaskCount--; unpaused.signalAll(); } finally { taskLock.unlock(); } } } 

Это должно быть достаточно для вас. Btw, оригинальная реализация была основана на размере задачи, потому что одна задача могла быть больше 100 раз, чем другая, и подавать две огромные задачи - убивать ящик, но запуск одного большого и большого количества был в порядке. Если ваши интенсивные задачи ввода-вывода примерно одинакового размера, вы можете использовать этот class, иначе просто сообщите мне, и я опубликую версию на основе размера.

PS Вы хотите проверить ThreadPoolExecutor javadoc. Это действительно приятное руководство пользователя от Doug Lea о том, как его можно легко настроить.

Я знаю, что это старый вопрос, но у него была аналогичная проблема: создание новых задач было очень быстрым, и если было слишком много OutOfMemoryError, потому что существующая задача не была выполнена достаточно быстро.

В моем случае Callables отправляются, и мне нужен результат, поэтому мне нужно сохранить все Futures возвращенные executor.submit() . Мое решение заключалось в том, чтобы поставить Futures в BlockingQueue с максимальным размером. Когда эта очередь заполнена, больше задач не создается до тех пор, пока некоторые не будут завершены (элементы удалены из очереди). В псевдокоде:

 final ExecutorService executor = Executors.newFixedThreadPool(numWorkerThreads); final LinkedBlockingQueue futures = new LinkedBlockingQueue<>(maxQueueSize); try { Thread taskGenerator = new Thread() { @Override public void run() { while (reader.hasNext) { Callable task = generateTask(reader.next()); Future future = executor.submit(task); try { // if queue is full blocks until a task // is completed and hence no future tasks are submitted. futures.put(compoundFuture); } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } executor.shutdown(); } } taskGenerator.start(); // read from queue as long as task are being generated // or while Queue has elements in it while (taskGenerator.isAlive() || !futures.isEmpty()) { Future compoundFuture = futures.take(); // do something } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } catch (ExecutionException ex) { throw new MyException(ex); } finally { executor.shutdownNow(); } 

Я реализовал решение по шаблону декоратора и с помощью семафора, чтобы контролировать количество выполненных задач. Вы можете использовать его с любым Executor и:

  • Укажите максимальное количество текущих задач
  • Укажите максимальный тайм-аут, чтобы дождаться разрешения на выполнение задачи (если тайм-аут прошел и не было получено разрешение, RejectedExecutionException исключение RejectedExecutionException )
 import static java.util.concurrent.TimeUnit.MILLISECONDS; import java.time.Duration; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import javax.annotation.Nonnull; public class BlockingOnFullQueueExecutorDecorator implements Executor { private static final class PermitReleasingDecorator implements Runnable { @Nonnull private final Runnable delegate; @Nonnull private final Semaphore semaphore; private PermitReleasingDecorator(@Nonnull final Runnable task, @Nonnull final Semaphore semaphoreToRelease) { this.delegate = task; this.semaphore = semaphoreToRelease; } @Override public void run() { try { this.delegate.run(); } finally { // however execution goes, release permit for next task this.semaphore.release(); } } @Override public final String toString() { return String.format("%s[delegate='%s']", getClass().getSimpleName(), this.delegate); } } @Nonnull private final Semaphore taskLimit; @Nonnull private final Duration timeout; @Nonnull private final Executor delegate; public BlockingOnFullQueueExecutorDecorator(@Nonnull final Executor executor, final int maximumTaskNumber, @Nonnull final Duration maximumTimeout) { this.delegate = Objects.requireNonNull(executor, "'executor' must not be null"); if (maximumTaskNumber < 1) { throw new IllegalArgumentException(String.format("At least one task must be permitted, not '%d'", maximumTaskNumber)); } this.timeout = Objects.requireNonNull(maximumTimeout, "'maximumTimeout' must not be null"); if (this.timeout.isNegative()) { throw new IllegalArgumentException("'maximumTimeout' must not be negative"); } this.taskLimit = new Semaphore(maximumTaskNumber); } @Override public final void execute(final Runnable command) { Objects.requireNonNull(command, "'command' must not be null"); try { // attempt to acquire permit for task execution if (!this.taskLimit.tryAcquire(this.timeout.toMillis(), MILLISECONDS)) { throw new RejectedExecutionException(String.format("Executor '%s' busy", this.delegate)); } } catch (final InterruptedException e) { // restore interrupt status Thread.currentThread().interrupt(); throw new IllegalStateException(e); } this.delegate.execute(new PermitReleasingDecorator(command, this.taskLimit)); } @Override public final String toString() { return String.format("%s[availablePermits='%s',timeout='%s',delegate='%s']", getClass().getSimpleName(), this.taskLimit.availablePermits(), this.timeout, this.delegate); } } 
  • Код для простого пула streamов в C #
  • Создание пула streamов с использованием boost
  • Доступ к областям профилированных объектов в streamах
  • Объединение streamов в C ++ 11
  • Как я могу отключить пулы исполнителей / планировщиков Spring Spring до того, как все остальные бобы в веб-приложении будут уничтожены?
  • Как настроить тонкую пул streamов для фьючерсов?
  • Как создать пул streamов, используя boost в C ++?
  • Когда использовать пул streamов в C #?
  • В чем смысл гибкости streamов в ASP.Net?
  • Где я могу создать и использовать ScheduledThreadPoolExecutor, TimerTask или Handler?
  • Как реализовать PriorityBlockingQueue с помощью ThreadPoolExecutor и настраиваемых задач
  • Давайте будем гением компьютера.