17

I am trying to understand how Java FixedThreadPool works in practice, but the docs do not answer my question.

Assume a simple scenario like:

ExecutorService ES= Executors.newFixedThreadPool(3);
List<Future> FL;
for(int i=1;i<=200;i++){
   FL.add(ES.submit(new Task()));
}
ES.shutdown();

where Task is a Callable which construct some resources, uses them, and returns some output.

My question: how many Task are there in memory upon completing the for loop? In other words: will there be only 3 Task at a time constructing their resources, or all of them are created upfront, such that, after .submit I have 200 Task (and their resources) waiting to execute?

Note: resources construction happens in the constructor of Task, not in the call() method.

In the javadoc (feel free to skip the following): what confuses me is the following explanation in the Java docs

Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. At any point, at most nThreads threads will be active processing tasks.

I suppose this means that, in my example, all the 200 Task are in the queue, but only 3 of them are executed at any time.

Any help is highly appreciated.

k88074
  • 2,042
  • 5
  • 29
  • 43
  • You are right after loop you have 200 created instance of `Task` in queue of pool, which executes 3 task at same time. – alex2410 Mar 26 '15 at 12:02
  • Your question cannot be answered, because this highly depends on if and when the garbage collector kicks in to collect completed tasks. – SpaceTrucker Mar 26 '15 at 12:18
  • fyi `newFixedThreadPool(nThreads)` is just convience method for `new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());` – Patrick Mar 26 '15 at 12:19
  • 1
    One thing nobody else mentioned: The thread pool has three threads. Those threads potentially will be removing tasks from the head of the queue and performing them _while your main thread is creating and submitting new tasks._ It is theoretically possible that the queue will never have more than one task in it. It is also theoretically possible that the main loop will complete, and there will be 200 tasks in the queue before the first background thread starts to run. Anything in between is possible too. – Solomon Slow Mar 26 '15 at 13:46
  • Are you asking whether `ES.submit` blocks? – Raedwald Mar 31 '15 at 06:32
  • It uses LinkedBlockingQueue( ) with no parameter.. thus max size is INTEGER.MAX_INT. So don't worry! If you use your own new ThreadPoolExecutor( ) and pass LinkedBlockingQueue(with some size), then if limit is crossed, you will get an exception. – Apurva Singh Jan 12 '18 at 16:48

5 Answers5

11

Your code is equivalent to

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

And after the for loop, the constructor of Task has thus been called 200 times, and the code it contains has thus been executed 200 times. Whether the task is submitted to an executor or not is irrelevant: you're invoking a constructor 200 times in a loop, and after each task has been constructed, it's submitted to the executor. The executor is not the one calling the task constructor.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • So, if I understand correctly, I have all the 200 Tasks in memory. What is actually done in parallel is the execution of the method `call()` of Task (which is a `Callable`). Therefore, I will have 3 parallel instances of the `call()` method (and the objects and resources it creates). Am I on the right track? – k88074 Mar 26 '15 at 13:27
  • 1
    The 3 threads in the pool indeed run concurrently, and call the `call()` method of the submitted tasks. Methods are not instantiated though, so saying that you have "3 instances of the `call()` method" is incorrect. – JB Nizet Mar 26 '15 at 13:29
  • `s/instances/invocations/` – John Kugelman Mar 26 '15 at 15:40
6

The tasks will be removed one by one from the queue, so as the execution proceed, the Tasks will be removed, and only the result of them will be stored in those Future objects.

So basically in the memory:

3 Threads
200 -> 0 Task
0 -> 200 Future
(with every executed tasks)

gaRos
  • 2,463
  • 6
  • 22
  • 27
4

You are creating 200 objects using new Task() and those tasks are submitted to executor. The executors hold a reference to this Task object. So, if in the constructor of the Task you are construct and holding resources then all the 200 Task will be holding the resources.

If possible, you can construct and use the resource in the call method of Task if you don't want 200 instances to construct and hold the resources. In that case, only 3 Task at a time will construct and hold the resource.

suranjan
  • 447
  • 2
  • 4
3

All 200 Tasks are created and consume the resources, and all of them are in the queue.

The thread pool, only invokes their run()/call() method one by one, when a free thread is available for the execution.

Zielu
  • 8,312
  • 4
  • 28
  • 41
3

To understand this, you will need to see what is happening when you are submitting the task to the Executor in a loop. First we will just look at the submission of a single task to the Executor. I will now be referring to the JDK 1.7.0_51 source code

The static method Executor.newFixedThreadPool method returns a ThreadPoolExecutor containing a blocking queue to hold the task

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

The moment you add a task to this Executor,this goes to the submit method of ThreadPoolExecutor extending AbstractExecutorService where the implementation of submit method is written.

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

The execute method is implementation specific ( meaning different types of Executor implement it different ways)

Now comes the real meat. This is the execute method defined in ThreadPoolExecutor. Especially pay attention at the comments. Here few configuration parameters of ThreadPoolExecutor like corePoolSize comes into play.

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);
    }
Shailendra
  • 8,874
  • 2
  • 28
  • 37