Detailed explanation of Java thread pool parameters and other creation methods

Posted by MCrosbie on Tue, 01 Feb 2022 19:09:16 +0100

With the increasing number of cpu cores, it is inevitable to use multithreading technology to make full use of its computing power. Therefore, multithreading technology is a technology that service developers must master. Thread creation and destruction involve system calls and consume system resources. Therefore, thread pool technology is introduced to avoid frequent thread creation and destruction. There is an Executors tool class in Java, which can create a thread pool for us. Its essence is to create a ThreadPoolExecutor object.

Create thread pool:

    public static void main(String[] args) {

        ExecutorService pool = new ThreadPoolExecutor(
                2,
                3,
                5L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<> (3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        try{
            for (int i = 0; i < 10; i++) {
                pool.execute(()->{
                    try {
                        TimeUnit.SECONDS.sleep(1L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+" start");
                });
            }
        }catch(Exception e){
          e.getMessage();
        }finally{
            pool.shutdown();
        }

    }

Introduction to source code parameters:

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

1, corePoolSize thread pool core thread size

A minimum number of threads will be maintained in the thread pool. Even if these threads handle idle state, they will not be destroyed unless allowCoreThreadTimeOut is set. The minimum number of threads here is corePoolSize.

2, maximumPoolSize maximum number of threads in the thread pool

After a task is submitted to the thread pool, it will first find out whether there is an idle living thread. If there is, it will be directly handed over to the idle thread for execution. If not, it will be cached in the work queue (described later). If the work queue is full, a new thread will be created, and then a task will be taken from the head of the work queue and handed over to the new thread for processing, The task just submitted is placed at the end of the work queue. The thread pool will not create new threads without limit. It will have a limit on the maximum number of threads, which is specified by maximunPoolSize.

3, keepAliveTime idle thread lifetime

If a thread is idle and the current number of threads is greater than corePoolSize, the idle thread will be destroyed after the specified time, which is set by keepAliveTime

4, Unit idle thread lifetime unit

Unit of measure for keepAliveTime

5, workQueue work queue

After a new task is submitted, it will enter this work queue first, and then take out the task from the queue when scheduling the task. There are four types of work queues available in the jdk:

1.ArrayBlockingQueue

Array based bounded blocking queue, sorted by FIFO. When a new task comes in, it will be placed at the end of the queue. A bounded array can prevent resource depletion. When the number of threads in the thread pool reaches the corePoolSize and a new task comes in, the task will be placed at the end of the queue and waiting to be scheduled. If the queue is full, a new thread will be created. If the number of threads has reached maxPoolSize, the reject policy will be executed.

2.LinkedBlockingQuene

Unbounded blocking queue based on linked list (in fact, the maximum capacity is integer. Max), sorted according to FIFO. Due to the approximate boundlessness of the queue, when the number of threads in the thread pool reaches corePoolSize, new tasks will come in and will be stored in the queue instead of creating new threads until maxPoolSize. Therefore, when using the work queue, the parameter maxPoolSize actually does not work.

3.SynchronousQuene

In a blocking queue that does not cache tasks, the producer puts in a task and must wait until the consumer takes out the task. That is, when a new task comes in, it will not be cached, but will be directly scheduled to execute the task. If there are no available threads, a new thread will be created. If the number of threads reaches maxPoolSize, the rejection policy will be executed.

4.PriorityBlockingQueue

Unbounded blocking queue with priority, which is realized by parameter Comparator.

6, threadFactory thread factory

The factory used when creating a new thread can be used to set the thread name, whether it is a daemon thread, and so on

7, handler reject policy:

1.AbortPolicy under this policy, the task is directly discarded and a RejectedExecutionException exception is thrown
2.CallerRunsPolicy under this policy, the run method of the rejected task is directly executed in the caller thread. Unless the thread pool has been shut down, the task is directly discarded.
3.DiscardPolicy under this policy, tasks are directly discarded and nothing is done.
4.DiscardOldestPolicy under this policy, discard the task that entered the queue the earliest, and then try to put the rejected task into the queue.

Thread pool principle:

1. Create a thread when the number of threads is less than the number of core threads.
2. When the number of threads is greater than or equal to the number of core threads and the task queue is not full, put the task into the task queue.
3. When the number of threads is greater than or equal to the number of core threads and the task queue is full.
(1) If the number of threads is less than the maximum number of threads, create a thread.
(2) If the number of threads is equal to the maximum number of threads, throw an exception and reject the task.

Advantages of thread pool:

1. Threads are scarce resources. Using thread pool can reduce the number of threads created and destroyed, and each working thread can be reused.
2. The number of working threads in the thread pool can be adjusted according to the affordability of the system to prevent the server from crashing due to excessive memory consumption.

Thread number setting:

This needs to be considered comprehensively according to the specific task and machine performance, and the optimal number of threads can be analyzed through continuous performance testing. Generally speaking:
1.CPU intensive: the cpu utilization rate is high, and the number of threads can be set to be 1 larger than the number of cpu cores. Make full use of cpu.
2.IO intensive: IO intensive, which mainly carries out long-time IO operations. The CPU utilization is not as high as that of CPU intensive. Generally, the number of threads is set to be twice that of CPU.
Reference formula:
The number of CPU cores / (1-blocking coefficient) blocking coefficient is between 0.8 and 0.9. For example, 8-core CPU 8 / (1-0.9) = 80 threads.

Other creation methods:

It's better not to use Executors to create a thread pool, but through ThreadPoolExecutor. Through this way, the writer will know more about the internal running rules of the thread pool and avoid memory overflow as much as possible. Various thread pool application scenarios: 1 Newcachedthreadpool: suitable for tasks with large amount of tasks but less time-consuming.
2.newFixedThreadPool: suitable for tasks with a fixed amount of tasks but time-consuming.
3.newScheduledThreadPool: it is suitable for executing scheduled tasks and repetitive tasks with specific fixed cycles.
4.newSingleThreadExecutor: suitable for scenarios where multiple tasks are executed sequentially.
5. Newworksteelingpool: suitable for time-consuming tasks.

 public static void main(String[] args) throws Exception {
       // ExecutorService executorService = Executors.newCachedThreadPool();
       // ExecutorService executorService = Executors.newFixedThreadPool(2);
       // ExecutorService executorService = Executors.newScheduledThreadPool(2);
       // ExecutorService executorService = Executors.newSingleThreadExecutor();
        ExecutorService executorService = Executors.newWorkStealingPool();

        try{
            for (int i = 0; i < 6; i++) {
                executorService.execute(new Thread(()->{
                    System.out.println("Run section" + Thread.currentThread().getName() + "Threads");

                },String.valueOf(i)));
            }
        }catch(Exception e){
          e.getMessage();
        }finally{
            executorService.shutdown();
        }
    }

 

Topics: Java Multithreading thread pool