我如何解决此限制,ThreadPoolExecutor
即在需要启动更多线程之前,队列必须被限制为已满。
我相信,我终于找到了针对此限制的某种优雅(也许有点怪异)的解决方案ThreadPoolExecutor
。它包括扩展LinkedBlockingQueue
使其返回false
的queue.offer(...)
时候,已经有一些排队的任务。如果当前线程跟不上排队的任务,则TPE将添加其他线程。如果该池已处于最大线程数,则将RejectedExecutionHandler
调用。然后是处理程序将其put(...)
放入队列。
编写一个offer(...)
可以返回false
并且put()
永不阻塞的队列肯定是很奇怪的,这就是hack的一部分。但这与TPE的队列使用情况很好,因此我认为这样做没有任何问题。
这是代码:
// extend LinkedBlockingQueue to force offer() to return false conditionally
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {
private static final long serialVersionUID = -6903933921423432194L;
@Override
public boolean offer(Runnable e) {
// Offer it to the queue if there is 0 items already queued, else
// return false so the TPE will add another thread. If we return false
// and max threads have been reached then the RejectedExecutionHandler
// will be called which will do the put into the queue.
if (size() == 0) {
return super.offer(e);
} else {
return false;
}
}
};
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1 /*core*/, 50 /*max*/,
60 /*secs*/, TimeUnit.SECONDS, queue);
threadPool.setRejectedExecutionHandler(new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
try {
// This does the actual put into the queue. Once the max threads
// have been reached, the tasks will then queue up.
executor.getQueue().put(r);
// we do this after the put() to stop race conditions
if (executor.isShutdown()) {
throw new RejectedExecutionException(
"Task " + r + " rejected from " + e);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
});
通过这种机制,当我将任务提交到队列时,ThreadPoolExecutor
将:
- 最初将线程数扩展到核心大小(此处为1)。
- 将其提供给队列。如果队列为空,则将其排队以由现有线程处理。
- 如果队列中已经有1个或多个元素,
offer(...)
则将返回false。
- 如果返回false,则按比例扩大池中的线程数量,直到达到最大数量(此处为50)。
- 如果达到最大值,则调用
RejectedExecutionHandler
- 在
RejectedExecutionHandler
随后会将任务到队列通过FIFO顺序第一个可用线程处理。
尽管在上面的示例代码中,队列是无界的,但是您也可以将其定义为有界队列。例如,如果您将容量添加到1000,LinkedBlockingQueue
则它将:
- 将线程放大到最大
- 然后排队直到满载1000个任务
- 然后阻止呼叫者,直到队列可用空间为止。
此外,如果您需要在中使用offer(...)
,
RejectedExecutionHandler
则可以offer(E, long, TimeUnit)
改用with Long.MAX_VALUE
作为超时方法。
警告:
如果您希望在执行器关闭后将任务添加到执行器中,那么当执行器服务已关闭时,您可能想更明智地RejectedExecutionException
放弃我们的习惯RejectedExecutionHandler
。感谢@RaduToader指出这一点。
编辑:
对这个答案的另一个调整可能是询问TPE是否有空闲线程,而只有在空闲线程时才排队。您必须为此创建一个真正的类并ourQueue.setThreadPoolExecutor(tpe);
在其上添加方法。
然后您的offer(...)
方法可能类似于:
- 检查
tpe.getPoolSize() == tpe.getMaximumPoolSize()
在这种情况下是否只需致电super.offer(...)
。
- 否则,
tpe.getPoolSize() > tpe.getActiveCount()
则调用,super.offer(...)
因为似乎有空闲线程。
- 否则返回
false
派生另一个线程。
也许这样:
int poolSize = tpe.getPoolSize();
int maximumPoolSize = tpe.getMaximumPoolSize();
if (poolSize >= maximumPoolSize || poolSize > tpe.getActiveCount()) {
return super.offer(e);
} else {
return false;
}
请注意,TPE上的get方法很昂贵,因为它们访问volatile
字段或(在的情况下getActiveCount()
)锁定TPE并遍历线程列表。同样,这里存在竞争条件,可能导致任务被不正确地排队,或者在存在空闲线程时分叉另一个线程。