了解Java固定线程池

了解Java固定线程池,java,multithreading,executorservice,callable,Java,Multithreading,Executorservice,Callable,我试图理解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(); Executors服务=Executors.newFixedTh

我试图理解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();
Executors服务=Executors.newFixedThreadPool(3);
列表FL;

对于(inti=1;i所有200个任务都被创建并消耗资源,并且它们都在队列中


线程池仅在有空闲线程可供执行时逐个调用其run()/call()方法。

您的代码相当于

for (int i = 1; i <= 200; i++){
    Task t = new Task();
    FL.add(ES.submit(t));
}

for(int i=1;i任务将从队列中逐个删除,因此随着执行的进行,任务将被删除,并且只有任务的结果将存储在这些未来的对象中

所以基本上在内存中:

3个线程
200->0任务
0->200未来

(对于每个已执行的任务)

您正在使用
新任务()
创建200个对象,这些任务将提交给执行者。执行者持有对该
任务的引用。
因此,如果在
任务的构造函数中
您正在构造并持有资源,那么所有200个任务都将持有资源


如果可能的话,如果您不想让200个实例来构造和保存资源,您可以在
Task
的调用方法中构造和使用资源。在这种情况下,一次只有3个
Task
将构造和保存资源。

要理解这一点,您需要查看提交任务时发生了什么给循环中的执行者。 首先,我们将只看向执行者提交一个任务。我现在将参考
jdk1.7.051
源代码

静态方法
Executor.newFixedThreadPool
方法返回一个
ThreadPoolExecutor
,其中包含一个用于保存任务的阻塞队列

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
execute方法是特定于实现的(意味着不同类型的执行器以不同的方式实现它)

现在是真正的问题了。这是
ThreadPoolExecutor
中定义的execute方法请特别注意注释。 在这里,ThreadPoolExecutor的一些配置参数,如
corePoolSize
起作用

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);
    }
public void execute(Runnable命令){
如果(命令==null)
抛出新的NullPointerException();
/*
*分三步进行:
*
*1.如果运行的线程少于corePoolSize,请尝试
*以给定命令作为第一个线程启动新线程
*对addWorker的调用以原子方式检查运行状态和
*workerCount,从而防止可能增加
*在不应该的情况下,通过返回false执行线程。
*
*2.如果任务可以成功排队,那么我们仍然需要
*再次检查是否应该添加线程
*(因为自上次检查以来,已有的已死亡)或
*自进入此方法后,池关闭。因此
*重新检查状态,如有必要,在以下情况下回滚排队
*已停止,如果没有线程,则启动新线程。
*
*3.如果无法对任务排队,则尝试添加新任务
*线程。如果它失败,我们知道我们已关闭或饱和
*所以拒绝这个任务。
*/
int c=ctl.get();
if(工作计数(c)
循环结束后,您在池队列中创建了200个
任务的实例,该实例同时执行3个任务。您的问题无法回答,因为这在很大程度上取决于垃圾收集器是否以及何时开始收集已完成的任务。仅供参考
newFixedThreadPool(nThreads)
只是
新ThreadPoolExecutor(nThreads,nThreads,0L,TimeUnit.ms,new LinkedBlockingQueue())的便捷方法
没有人提到的一件事:线程池有三个线程。这些线程可能会从队列头删除任务,并在主线程创建和提交新任务时执行这些任务。从理论上讲,队列中可能永远不会有多个任务。从理论上讲,这也是p主循环可能会完成,在第一个后台线程开始运行之前,队列中将有200个任务。中间的任何任务都是可能的。您是否询问
ES.submit
块?因此,如果我理解正确,我将所有200个任务都存储在内存中。实际上并行执行的是m任务的method
call()
(这是一个
可调用的
)。因此,我将有3个
call()
方法的并行实例(以及它创建的对象和资源)。我在正确的轨道上吗?池中的3个线程确实同时运行,并调用
call()
已提交任务的方法。但方法未实例化,因此说您有“3个
call()
方法实例”是不正确的。
s/instances/invocations/
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);
    }