我试图理解Java FixedThreadPool在实践中是如何工作的,但是文档没有回答我的问题。
假设一个简单的场景,如:
ExecutorService ES= Executors.newFixedThreadPool(3);
List<Future> FL;
for(int i=1;i<=200;i++){
FL.add(ES.submit(new Task()));
}
ES.shutdown();
其中Task
是Callable
,它构造一些资源,使用它们并返回一些输出。
我的问题:完成Task
循环后内存中有多少for
?换句话说:一次只有3 Task
构建资源,或者所有资源都是预先创建的,这样,.submit
我有200 Task
(及其资源)等待执行?
注意:资源构建发生在Task
的构造函数中,而不是call()
方法中。
在javadoc (随意跳过以下内容):让我感到困惑的是Java文档中的以下说明
创建一个重用固定数量的线程的线程池 关闭共享的无界队列。在任何时候,最多nThreads线程 将是积极的处理任务。
我想这意味着,在我的例子中,所有200个任务都在队列中,但其中只有3个随时执行。
非常感谢任何帮助。
答案 0 :(得分:10)
您的代码等同于
for (int i = 1; i <= 200; i++){
Task t = new Task();
FL.add(ES.submit(t));
}
在for循环之后,Task的构造函数因此被调用了200次,因此它包含的代码被执行了200次。是否将任务提交给执行者是无关紧要的:您在循环中调用构造函数200次,并且在每个任务构建完成后,将其提交给遗嘱执行人。执行者不是调用任务构造函数的人。
答案 1 :(得分:6)
任务将从队列中逐个删除,因此在执行过程中,任务将被删除,只有它们的结果将存储在那些Future对象中。
所以基本上在记忆中:
3个螺纹
200 - &gt; 0任务
0 - &gt; 200未来
(每个执行的任务)
答案 2 :(得分:4)
您正在使用new Task()
创建200个对象,并将这些任务提交给执行者。执行程序包含对此Task
对象的引用。
因此,如果在Task
的构造函数中构建并保存资源,则所有200任务都将保留资源。
如果可能,如果您不希望200个实例构建和保留资源,则可以在Task
的调用方法中构建和使用该资源。在这种情况下,一次只有3 Task
将构建并保留资源。
答案 3 :(得分:3)
创建所有200个任务并使用资源,所有这些资源都在队列中。
当一个空闲线程可用于执行时,线程池只会逐个调用它们的run()/ call()方法。
答案 4 :(得分:3)
要理解这一点,您需要查看在循环中将任务提交给Executor时发生的情况。
首先,我们将看一下向Executor提交单个任务。我现在将引用JDK 1.7.0_51
源代码
静态方法Executor.newFixedThreadPool
方法返回一个ThreadPoolExecutor
,其中包含一个阻塞队列来保存任务
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
当您向此Executor添加任务时,这将转到ThreadPoolExecutor
的提交方法AbstractExecutorService
,其中写入了提交方法的实现。
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
execute方法是特定于实现的(意味着不同类型的Executor以不同的方式实现)
现在真正的肉。这是ThreadPoolExecutor
中定义的执行方法。 特别注意评论。
这里很少有像corePoolSize
这样的ThreadPoolExecutor的配置参数。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
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);
}