ChatGPT解决这个技术问题 Extra ChatGPT

Core pool size vs maximum pool size in ThreadPoolExecutor

What exactly is the difference between core pool size and maximum pool size when we talk in terms of ThreadPoolExecutor?
Can it be explained with the help of an example?


C
Community

From this blog post:

Take this example. Starting thread pool size is 1, core pool size is 5, max pool size is 10 and the queue is 100. As requests come in, threads will be created up to 5 and then tasks will be added to the queue until it reaches 100. When the queue is full new threads will be created up to maxPoolSize. Once all the threads are in use and the queue is full tasks will be rejected. As the queue reduces, so does the number of active threads.


Is this correct? I thought new threads will be created until it reaches maxPoolSize. Then any new threads will be put in the queue. Please correct me if I'm wrong..
There is an interesting method allowCoreThreadTimeOut(boolean) which allows core threads to be killed after given idle time. Setting this to true and setting core threads = max threads allows the thread pool to scale between 0 and max threads.
You just copied it from here bigsoft.co.uk/blog/index.php/2009/11/27/…
You can use prestartCoreThread() or prestartAllCoreThreads() if you want to jump-start the number of threads running initially.
What happens to the rejected tasks?
D
Darshan Dalwadi

IF running threads > corePoolSize & < maxPoolSize, then create a new Thread if Total task queue is full and new one is arriving.

From doc: (If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full.)

Now, Take a simple example,

ThreadPoolExecutor executorPool = new ThreadPoolExecutor(5, 10, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(50));

Here, 5 is the corePoolSize - means Jvm will create new thread for new task for first 5 tasks. and other tasks will be added to the queue until queue is getting full (50 tasks).

10 is the maxPoolSize - JVM can create max 10 threads. Means if there are already 5 task/thread is running and queue is full with 50 pending tasks and if one more new request/task is arriving in queue then JVM will create new thread up to 10 (total threads=previous 5 + new 5);

new ArrayBlockingQueue(50) = is a total queue size - it can queue 50 tasks in it.

once all 10 threads are running and if new task is arriving then that new task will be rejected.

Rules for creating Threads internally by SUN:

If the number of threads is less than the corePoolSize, create a new Thread to run a new task. If the number of threads is equal (or greater than) the corePoolSize, put the task into the queue. If the queue is full, and the number of threads is less than the maxPoolSize, create a new thread to run tasks in. If the queue is full, and the number of threads is greater than or equal to maxPoolSize, reject the task.

Hope, This is HelpFul.. and please correct me if i'm wrong...


P
Premraj

Source

Rules of a ThreadPoolExecutor pool size

The rules for the size of a ThreadPoolExecutor's pool are generally miss-understood, because it doesn't work the way that you think it ought to or in the way that you want it to.

Take this example. Starting thread pool size is 1, core pool size is 5, max pool size is 10 and the queue is 100.

Sun's way: as requests come in threads will be created up to 5, then tasks will be added to the queue until it reaches 100. When the queue is full new threads will be created up to maxPoolSize. Once all the threads are in use and the queue is full tasks will be rejected. As the queue reduces so does the number of active threads.

User anticipated way: as requests come in threads will be created up to 10, then tasks will be added to the queue until it reaches 100 at which point they are rejected. The number of threads will rename at max until the queue is empty. When the queue is empty the threads will die off until there are corePoolSize left.

The difference is that the users want to start increasing the pool size earlier and want the queue to be smaller, where as the Sun method want to keep the pool size small and only increase it once the load becomes to much.

Here are Sun's rules for thread creation in simple terms:

If the number of threads is less than the corePoolSize, create a new Thread to run a new task. If the number of threads is equal (or greater than) the corePoolSize, put the task into the queue. If the queue is full, and the number of threads is less than the maxPoolSize, create a new thread to run tasks in. If the queue is full, and the number of threads is greater than or equal to maxPoolSize, reject the task. The long and the short of it is that new threads are only created when the queue fills up, so if you're using an unbounded queue then the number of threads will not exceed corePoolSize.

For a fuller explanation, get it from the horses mouth: ThreadPoolExecutor API documentation.

There is a really good forum post which talks you through the way that the ThreadPoolExecutor works with code examples: http://forums.sun.com/thread.jspa?threadID=5401400&tstart=0

More info: http://forums.sun.com/thread.jspa?threadID=5224557&tstart=450


Thank you. I like your answer a lot. Spills the light on the underlying implementation and answers the questions that SHOULD be asked and answered in order to really understand this corePoolSize/maxPoolSize behavior of the thread pool.
B
Brian Agnew

From the doc:

When a new task is submitted in method execute(java.lang.Runnable), and fewer than corePoolSize threads are running, a new thread is created to handle the request, even if other worker threads are idle. If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full.

Furthermore:

By setting corePoolSize and maximumPoolSize the same, you create a fixed-size thread pool. By setting maximumPoolSize to an essentially unbounded value such as Integer.MAX_VALUE, you allow the pool to accommodate an arbitrary number of concurrent tasks. Most typically, core and maximum pool sizes are set only upon construction, but they may also be changed dynamically using setCorePoolSize(int) and setMaximumPoolSize(int).


1)When a new task is submitted in method execute(java.lang.Runnable),and fewer than corePoolSize threads are running,a new thread is created to handle the request,even if other worker threads are idle. Why there is a need to create a new thread to handle the request if there are idle threads?
2) If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full. I don't understand the difference between corePoolSize and maximumPoolSize here. Secondly, how can a queue is full when threads are less than maximumPoolSize? Queue can only be full if threads are equal to maximumPoolSize. Isn't it?
c
cegas

If you decide to create a ThreadPoolExecutor manually instead of using the Executors factory class, you will need to create and configure one using one of its constructors. The most extensive constructor of this class is:

public ThreadPoolExecutor(
    int corePoolSize,
    int maxPoolSize,
    long keepAlive,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    RejectedExecutionHandler handler
);

As you can see, you can configure:

The core pool size (the size the thread pool will try to stick with).

The maximum pool size.

The keep alive time, which is a time after which an idle thread is eligible for being torn down.

The work queue to hold tasks awaiting execution.

The policy to apply when a task submission is rejected.

Limiting the Number of Queued Tasks

Limiting the number of concurrent tasks being executing, sizing your thread pool, represents a huge benefit for your application and its execution environment in terms of predictability and stability: an unbounded thread creation will eventually exhaust the runtime resources and your application might experience as a consequence, serious performance problems that may lead even to application instability.

That's a solution to just one part of the problem: you're capping the number of tasks being executed but aren't capping the number of jobs that can be submitted and enqueued for later execution. The application will experience resource shortage later, but it will eventually experience it if the submission rate consistently outgrows the execution rate.

The solution to this problem is: Providing a blocking queue to the executor to hold the awaiting tasks. In the case the queue fills up, the submitted task will be "rejected". The RejectedExecutionHandler is invoked when a task submission is rejected, and that's why the verb rejected was quoted in the previous item. You can implement your own rejection policy or use one of the built-in policies provided by the framework.

The default rejection policies has the executor throw a RejectedExecutionException. However, other built-in policies let you:

Discard a job silently.

Discard the oldest job and try to resubmit the last one.

Execute the rejected task on the caller's thread.


S
Saurav Sahu

In picture, consider only addition of tasks are happening

https://i.stack.imgur.com/vQccY.png


2
2 revs

You can find the definition of the terms corepoolsize and maxpoolsize in the javadoc. http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/ThreadPoolExecutor.html

The link above has the answer to your question. However, just to make it clear. The application will keep creating threads till it reaches the corePoolSize. I think the idea here is that these many threads should be sufficient to handle the inflow of tasks. If a new task comes after the corePoolSize threads are created the tasks will be queued. Once the queue is full the executor will start creating new threads. It is kind of balancing. What it essentially means is that the inflow of tasks is more than the processing capacity. So, Executor will start creating new threads again till it reaches Max number of threads. Again, a new threads will be created if and only if the queue is full.


c
cs95

Good explanation in this blog:

Illustration

public class ThreadPoolExecutorExample {

    public static void main (String[] args) {
        createAndRunPoolForQueue(new ArrayBlockingQueue<Runnable>(3), "Bounded");
        createAndRunPoolForQueue(new LinkedBlockingDeque<>(), "Unbounded");
        createAndRunPoolForQueue(new SynchronousQueue<Runnable>(), "Direct hand-off");
    }

    private static void createAndRunPoolForQueue (BlockingQueue<Runnable> queue,
                                                                      String msg) {
        System.out.println("---- " + msg + " queue instance = " +
                                                  queue.getClass()+ " -------------");

        ThreadPoolExecutor e = new ThreadPoolExecutor(2, 5, Long.MAX_VALUE,
                                 TimeUnit.NANOSECONDS, queue);

        for (int i = 0; i < 10; i++) {
            try {
                e.execute(new Task());
            } catch (RejectedExecutionException ex) {
                System.out.println("Task rejected = " + (i + 1));
            }
            printStatus(i + 1, e);
        }

        e.shutdownNow();

        System.out.println("--------------------\n");
    }

    private static void printStatus (int taskSubmitted, ThreadPoolExecutor e) {
        StringBuilder s = new StringBuilder();
        s.append("poolSize = ")
         .append(e.getPoolSize())
         .append(", corePoolSize = ")
         .append(e.getCorePoolSize())
         .append(", queueSize = ")
         .append(e.getQueue()
                  .size())
         .append(", queueRemainingCapacity = ")
         .append(e.getQueue()
                  .remainingCapacity())
         .append(", maximumPoolSize = ")
         .append(e.getMaximumPoolSize())
         .append(", totalTasksSubmitted = ")
         .append(taskSubmitted);

        System.out.println(s.toString());
    }

    private static class Task implements Runnable {

        @Override
        public void run () {
            while (true) {
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    break;
                }
            }
        }
    }
}

Output :

---- Bounded queue instance = class java.util.concurrent.ArrayBlockingQueue -------------
poolSize = 1, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 3, maximumPoolSize = 5, totalTasksSubmitted = 1
poolSize = 2, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 3, maximumPoolSize = 5, totalTasksSubmitted = 2
poolSize = 2, corePoolSize = 2, queueSize = 1, queueRemainingCapacity = 2, maximumPoolSize = 5, totalTasksSubmitted = 3
poolSize = 2, corePoolSize = 2, queueSize = 2, queueCapacity = 1, maximumPoolSize = 5, totalTasksSubmitted = 4
poolSize = 2, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 5
poolSize = 3, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 6
poolSize = 4, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 7
poolSize = 5, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 8
Task rejected = 9
poolSize = 5, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 9
Task rejected = 10
poolSize = 5, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 10
--------------------

---- Unbounded queue instance = class java.util.concurrent.LinkedBlockingDeque -------------
poolSize = 1, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 2147483647, maximumPoolSize = 5, totalTasksSubmitted = 1
poolSize = 2, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 2147483647, maximumPoolSize = 5, totalTasksSubmitted = 2
poolSize = 2, corePoolSize = 2, queueSize = 1, queueRemainingCapacity = 2147483646, maximumPoolSize = 5, totalTasksSubmitted = 3
poolSize = 2, corePoolSize = 2, queueSize = 2, queueRemainingCapacity = 2147483645, maximumPoolSize = 5, totalTasksSubmitted = 4
poolSize = 2, corePoolSize = 2, queueSize = 3, queueRemainingCapacity = 2147483644, maximumPoolSize = 5, totalTasksSubmitted = 5
poolSize = 2, corePoolSize = 2, queueSize = 4, queueRemainingCapacity = 2147483643, maximumPoolSize = 5, totalTasksSubmitted = 6
poolSize = 2, corePoolSize = 2, queueSize = 5, queueRemainingCapacity = 2147483642, maximumPoolSize = 5, totalTasksSubmitted = 7
poolSize = 2, corePoolSize = 2, queueSize = 6, queueRemainingCapacity = 2147483641, maximumPoolSize = 5, totalTasksSubmitted = 8
poolSize = 2, corePoolSize = 2, queueSize = 7, queueRemainingCapacity = 2147483640, maximumPoolSize = 5, totalTasksSubmitted = 9
poolSize = 2, corePoolSize = 2, queueSize = 8, queueRemainingCapacity = 2147483639, maximumPoolSize = 5, totalTasksSubmitted = 10
--------------------

---- Direct hand-off queue instance = class java.util.concurrent.SynchronousQueue -------------
poolSize = 1, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 1
poolSize = 2, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 2
poolSize = 3, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 3
poolSize = 4, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 4
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 5
Task rejected = 6
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 6
Task rejected = 7
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 7
Task rejected = 8
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 8
Task rejected = 9
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 9
Task rejected = 10
poolSize = 5, corePoolSize = 2, queueSize = 0, queueRemainingCapacity = 0, maximumPoolSize = 5, totalTasksSubmitted = 10
--------------------


Process finished with exit code 0

R
Ramesh Papaganti

From the book Java concurency essentials :

CorePoolSize: The ThreadPoolExecutor has an attribute corePoolSize that determines how many threads it will start until new threads are only started when the queue is full

MaximumPoolSize: This attribute determines how many threads are started at the maximum. You can set this to Integer. MAX_VALUE in order to have no upper boundary


宏杰李

java.util.concurrent.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);
    }

A
Andrew

Understanding the internal behavior of the ThreadPoolExecutor when a new task is submitted helped me understand how corePoolSize and maximumPoolSize differ.

Let:

N be the number of threads in the pool, getPoolSize(). Active threads + idle threads.

T be the amount of tasks submitted to the executor/pool.

C be the core pool size, getCorePoolSize(). How many threads can at most be created per pool for the incoming tasks before new tasks go to the queue.

M be the maximum pool size, getMaximumPoolSize(). Maximum amount of threads the pool can allocate.

Behaviors of the ThreadPoolExecutor in Java when a new task is submitted:

For N <= C, the idle threads are not assigned the new incoming task, instead a new thread is created.

For N > C and if there are idle threads then new task is assigned there.

For N > C and if there are NO idle threads, new tasks are put into the queue. NO NEW THREAD CREATED HERE.

When queue is full, we create new threads up to M. If M is reached, we reject the tasks. What's important to not here is that we do not create new threads until the queue is full!

Sources:

docs.oracle.com

excellent article on the subject.

Examples

Example with corePoolSize = 0 and maximumPoolSize = 10 with a queue capacity of 50.

This will result in one single active thread in the pool until the queue has 50 items in it.

executor.execute(task #1):

before task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]

after task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]

[task #1 immediately queued and kicked in b/c the very first thread is created when `workerCountOf(recheck) == 0`]

execute(task #2):

before task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

after task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@c52dafe[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]

[task #2 not starting before #1 is done]

... executed a few tasks...

execute(task #19)

before task #19 submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 1, active threads = 1, queued tasks = 17, completed tasks = 0]

after task #19 submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 1, active threads = 1, queued tasks = 18, completed tasks = 0]

...

execute(task #51)

before task submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 1, active threads = 1, queued tasks = 50, completed tasks = 0]

after task submitted to executor: java.util.concurrent.ThreadPoolExecutor@735afe38[Running, pool size = 2, active threads = 2, queued tasks = 50, completed tasks = 0]

Queue is full.
A new thread was created as the queue was full.

Example with corePoolSize = 10 and maximumPoolSize = 10 with a queue capacity of 50.

This will result in 10 active threads in the pool. When the queue has 50 items in it, tasks will be rejected.

execute(task #1)

before task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]

after task #1 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

execute(task #2)

before task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]

after task #2 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]

execute(task #3)

before task #3 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0]

after task #3 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 3, active threads = 3, queued tasks = 0, completed tasks = 0]

... executed a few tasks...

execute(task #11)

before task #11 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 10, active threads = 10, queued tasks = 0, completed tasks = 0]

after task #11 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 10, active threads = 10, queued tasks = 1, completed tasks = 0]

... executed a few tasks...

execute(task #51)
before task #51 submitted to executor: java.util.concurrent.ThreadPoolExecutor@32d9e072[Running, pool size = 10, active threads = 10, queued tasks = 50, completed tasks = 0]

Task was rejected as we have reached `maximumPoolSize`. 


A
Ankit kaushik

As per the documentation :

Any BlockingQueue may be used to transfer and hold submitted tasks. The use of this queue interacts with pool sizing: If fewer than core pool size threads are running, the Executor always prefers adding a new thread rather than queuing. If core pool size or more threads are running, the Executor always prefers queuing a request rather than adding a new thread. If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.

This means core pool size is the threshold beyond which executor service prefers to queue up the task than spawn a new thread. If when receiving a task, the number of threads are less than the core pool size, the creation of a new thread is done thereby increasing active threads.