Thread pool parameter meaning and common setting strategies

Posted by cirene on Sun, 26 Dec 2021 21:24:28 +0100

summary

This paper summarizes the meaning of parameters in thread pool construction methods and common methods of setting thread pool parameters.

Parameter meaning

ThreadPoolExecutor contains four construction methods in total. The following methods are finally called. The meaning of parameters is as follows:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 

corePoolSize

The number of core threads. When the number of threads is less than the number of core threads, the thread pool gives priority to creating core threads to process tasks

By default, the core thread will always survive. If allowsCoreThreadTimeOut = true is set, the number of core threads will also be recycled when idle.

maximumPoolSize

The maximum number of threads. When the number of threads > = corepoolsize and the task queue is full. The thread pool creates new threads to process tasks.

When the number of bus processes = maximumPoolSize, if the task queue is full, the thread pool will execute RejectedExecutionHandler rejection policy for subsequent added tasks

keepAliveTime,TimeUnit

keepAliveTime indicates the maximum idle time of a thread, that is, when a thread exceeds keepAliveTime and has no task processing, the thread pool will recycle the idle threads until the number of threads = corePoolSize

If allowsCoreThreadTimeOut = true is set, the core thread will also be recycled when it exceeds its idle time.

TimeUnit refers to the unit of time, which can be days, hours, minutes and seconds. It is mainly used with keepAliveTime

workQueue

For the blocked task queue, when the number of working threads = corePoolSize, subsequent tasks will be added to the task queue first, and then scheduled and processed by the thread pool.

Common task queues are divided into the following types according to bounded and unbounded, blocking and non blocking:

threadFactory

To create a thread factory, you can implement the ThreadFactory interface to construct threads and set custom thread names, priorities, etc.

When the threadFactory implementation is not specified, the thread pool uses executors by default Defaultthreadfactory () is the creation thread. The default name of the created thread is pool XXX thread -, which is not a daemon thread and has the default priority.

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
	this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
	     Executors.defaultThreadFactory(), handler);
}

public class Executors {
    static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
}

RejectedExecutionHandler

Task rejection policy: when the task queue is full and the number of working threads has reached maximumPoolSize, the thread pool has no ability to process new tasks. At this time, the corresponding task rejection policy needs to be executed.

AbortPolicy is the default rejection policy

Static factory method

The Executors class provides several static ways to create thread pools

// coreSize, like maximumPoolSize, uses unbounded blocking queues
// When there is no task execution, the core thread will not be destroyed, occupying certain system resources
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}

// Single thread, using unbounded blocking queue
// Ensure that the task submission sequence is the same as the execution sequence
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

// There are no core threads, which are placed in the synchronization queue. When the number of requests is large, more threads are created
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

// Delay queue
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue(), threadFactory);
}

// Added after 1.8,
public static ExecutorService newWorkStealingPool(int parallelism) {
    return new ForkJoinPool
        (parallelism,
         ForkJoinPool.defaultForkJoinWorkerThreadFactory,
         null, true);
}

How to set reasonable thread pool parameters?

corePoolSize selection

The corePoolSize is determined according to whether the task is CPU intensive or IO intensive

CPU intensive tasks represent scenarios that require a large number of calculations. Generally, corePoolSize = number of physical machine CPU cores + 1; Too many thread settings may cause frequent thread context switching, so close to the number of physical machine CPU cores.

  • Why + 1? Avoid wasting CPU clock cycles by interrupting and pausing a thread for some reason.

IO intensive tasks represent scenarios where the program reads and writes to the hard disk and memory frequently. At this time, the CPU utilization is relatively low. Generally, set corePoolSize = number of physical machine CPU cores * 2

workQueue selection

Unbounded queue, that is, there is no limit on the length of the queue. When unbounded queue is used, the task rejection policy will fail, and with the accumulation of tasks, the service will be in danger of OOM. Therefore, use it with caution. LinkedBlockingQueue is a commonly used unbounded queue.

Bounded queue: the length of bounded queue needs to be specified during initialization, which can effectively avoid resource depletion. ArrayBlockingQueue and PriorityBlockingQueue are bounded blocking queues, and ArrayBlockingQueue meets FIFO first in first out principle; The elements in the PriorityBlockingQueue are sorted according to the priority of the task

Synchronous handover queue is a blocking queue that does not store elements. It must wait for the added elements to be consumed before continuing to add elements. In general, it requires maximumpoolsize = integer MAX_ Value, so that the thread pool will create new threads to process tasks.

RejectedExecutionHandler selection

If the task is less important and can be discarded, it is recommended to use the policies of DiscardPolicy and DiscardOldestPolicy

The default value of this parameter shall be used as much as possible, because the rejection policy in the thread pool can generally be avoided by setting the previous parameters.

Topics: thread pool