我正在尝试编写一个解决方案,其中单个线程生成可以并行执行的I / O密集型任务。每个任务都有重要的内存数据。所以我希望能够限制暂时待处理的任务数量。
如果我像这样创建ThreadPoolExecutor:
ThreadPoolExecutor executor = new ThreadPoolExecutor(numWorkerThreads, numWorkerThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(maxQueue));
然后当队列填满并且所有线程都已忙时,executor.submit(callable)
抛出RejectedExecutionException
。
当队列已满且所有线程都忙时,如何阻止executor.submit(callable)
阻止?
修改: 我试过this:
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
它有点实现了我想要实现的效果,但是以一种不雅的方式(基本上被拒绝的线程在调用线程中运行,因此这会阻止调用线程提交更多)。
编辑:(提问后5年)
对于阅读此问题及其答案的任何人,请不要将接受的答案作为一个正确的解决方案。请仔细阅读所有答案和评论。
答案 0 :(得分:60)
我做了同样的事情。诀窍是创建一个BlockingQueue,其中offer()方法实际上是put()。 (你可以使用你想要的任何基础BlockingQueue impl。)。
public class LimitedQueue<E> extends LinkedBlockingQueue<E>
{
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;
}
}
请注意,这仅适用于corePoolSize==maxPoolSize
的线程池,因此请注意(请参阅注释)。
答案 1 :(得分:15)
以下是我在最后解决这个问题的方法:
(注意:此解决方案会阻止提交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<Runnable>());
semaphore = new Semaphore(bound);
}
/**Submits task to execution pool, but blocks while number of running threads
* has reached the bound limit
*/
public <T> Future<T> submitButBlockIfFull(final Callable<T> task) throws InterruptedException{
semaphore.acquire();
return submit(task);
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
semaphore.release();
}
}
答案 2 :(得分:10)
当前接受的答案有一个潜在的重大问题 - 它改变了ThreadPoolExecutor.execute的行为,这样如果你有corePoolSize < maxPoolSize
,ThreadPoolExecutor逻辑将永远不会在核心之外添加额外的工作者。
从ThreadPoolExecutor。执行(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);
具体来说,最后一个&#39;否则&#39;阻止willl永远不会被击中。
更好的选择是做类似于OP已经做的事情 - 使用RejectedExecutionHandler来执行相同的put
逻辑:
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);
}
}
正如评论中所指出的(参考this answer),这种方法有一些值得注意的事项:
corePoolSize==0
,则存在竞争条件,其中池中的所有线程可能在任务可见之前死亡ThreadPoolExecutor
)将导致问题,除非处理程序也以相同的方式包装它。记住这些问题,此解决方案适用于大多数典型的ThreadPoolExecutors,并将正确处理corePoolSize < maxPoolSize
的情况。
答案 3 :(得分:4)
如果您使用的是 spring-integration,如何使用 CallerBlocksPolicy
类?
该类实现了 RejectedExecutionHandler
接口,该接口是用于处理无法由 ThreadPoolExecutor
执行的任务的处理程序。
您可以像这样使用此政策。
executor.setRejectedExecutionHandler(new CallerBlocksPolicy());
CallerBlocksPolicy
和 CallerRunsPolicy
的主要区别在于它是在调用者线程中阻塞还是运行任务。
请参阅this code。
答案 4 :(得分:2)
我遇到了类似的问题,我使用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<Runnable> 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();
}
}
}
这对你来说应该足够好了。顺便说一下,最初的实现是基于任务大小的,因为一个任务可能比另一个任务大100倍,并且提交两个巨大的任务就是杀死了这个盒子,但是运行一个大而且大量的小就是好的。如果您的I / O密集型任务的大小大致相同,则可以使用此类,否则只需告诉我,我将发布基于大小的实现。
P.S。您需要检查ThreadPoolExecutor
javadoc。 Doug Lea关于如何轻松定制它是非常好的用户指南。
答案 5 :(得分:2)
我知道这是一个老问题,但有一个类似的问题,即创建新任务的速度非常快,如果出现太多OutOfMemoryError,因为现有任务没有足够快地完成。
在我的情况下Callables
已提交,我需要结果,因此我需要存储Futures
返回的所有executor.submit()
。我的解决方案是将Futures
放入最大尺寸的BlockingQueue
。一旦该队列已满,在完成某些任务(从队列中删除元素)之前不会再生成任务。在伪代码中:
final ExecutorService executor = Executors.newFixedThreadPool(numWorkerThreads);
final LinkedBlockingQueue<Future> 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();
}
答案 6 :(得分:1)
我认为这就像使用ArrayBlockingQueue
而不是LinkedBlockingQueue
一样简单。
忽略我......那是完全错误的。 ThreadPoolExecutor
调用Queue#offer
而非put
,这会产生您需要的效果。
您可以扩展ThreadPoolExecutor
并提供execute(Runnable)
的实施,以put
代替offer
。
我担心这似乎不是一个完全令人满意的答案。
答案 7 :(得分:1)
我已经在装饰器模式之后实现了一个解决方案,并使用信号量来控制执行任务的数量。您可以将其与任何Executor
和:
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);
}
}