Dry goods sharing JVM Part 2 - thread pool

Posted by WiseGuy on Sat, 29 Jan 2022 05:58:42 +0100

To view the previous blogs, click [category column] at the top

 

What is a thread pool?

Thread pool in Java is the concurrency framework with the most application scenarios. Almost all programs that need to execute tasks asynchronously or concurrently can use thread pool. In the development process, the rational use of thread pool can bring three benefits.
1. Reduce resource consumption. The created thread resources can be reused to avoid resource consumption caused by frequent thread creation and destruction.
2. Improve response speed. When a request arrives, it can be directly allocated from the thread pool to execute without re creating the thread.
3. Improve thread manageability. Threads are scarce resources. If they are created without restrictions, they will not only consume system resources, but also reduce the stability of the system. Using thread pool can be uniformly allocated, tuned and monitored. However, in order to make rational use of thread pool, we must know its implementation principle like the back of our hand.

 

Classification of thread pool (5 kinds)

1. newSingleThreadExecutor - thread pool for a single thread

Create a singleton thread pool, which will only use a unique working thread to execute tasks. It can be used in scenarios that need to ensure sequential execution, and ensure that all tasks are executed in the specified order (FIFO, LIFO, priority).

Example:

        //One sentence of code creates the thread pool
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        //usage method
        for (int i = 0; i < 5; i++) {
            final int k = i;
            singleThreadExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"-" + k);
                    try {
                        Thread.sleep(200);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });

        }

Output:

pool-1-thread-1-0
pool-1-thread-1-1
pool-1-thread-1-2
pool-1-thread-1-3
pool-1-thread-1-4

 

2. newFixedThreadPool

Create a fixed length thread pool to control the maximum concurrent number of threads. The exceeded threads will wait in the queue.

Next, create a thread pool with a size of 5.

        ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int k = i;
            newFixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"-" + k);
                }
            });
        }

Output: you can see the prefix of thread pool, up to 5.

pool-1-thread-1-0
pool-1-thread-2-1
pool-1-thread-3-2
pool-1-thread-2-6
pool-1-thread-1-5
pool-1-thread-1-9
pool-1-thread-2-8
pool-1-thread-3-7
pool-1-thread-4-3
pool-1-thread-5-4

 

3. newCachedThreadPool - scalable thread pool (common)

Create a cacheable thread pool. If the length of the thread pool exceeds the processing needs, you can flexibly recycle idle threads. If there is no recyclable thread, you can create a new thread. JVM auto recycle.

Example:

        // Unlimited thread pool jvm auto recycle
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int k = i;
            newCachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName() + "-" + k);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }

Output: you can see that the thread pool prefix is expanding.

pool-1-thread-1-0
pool-1-thread-2-1
pool-1-thread-3-2
pool-1-thread-4-3
pool-1-thread-5-4
pool-1-thread-6-5
pool-1-thread-7-6
pool-1-thread-8-7
pool-1-thread-9-8
pool-1-thread-10-9

 

4. newScheduledThreadPool , delay and start thread pool regularly

Create a fixed length thread pool to support regular and periodic task execution. It is suitable for scenarios that require multiple background threads to execute periodic tasks.

Example: create a thread pool with a length of 5 and delay the execution for 3 seconds.

        ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 10; i++) {
            final int k = i;
            newScheduledThreadPool.schedule(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "-" + k);
                }
            }, 3, TimeUnit.SECONDS);
        }

Output:

pool-1-thread-2-1
pool-1-thread-3-2
pool-1-thread-2-5
pool-1-thread-5-4
pool-1-thread-4-3
pool-1-thread-1-0
pool-1-thread-4-9
pool-1-thread-5-8
pool-1-thread-2-7
pool-1-thread-3-6

 

5. Newworksteelingpool is a thread pool of multiple task queues

Newworksteelingpool is jdk1 8. It can dynamically create and close threads according to the required parallel level, reduce competition by using multiple queues, and realize it with ForkJoinPool at the bottom. The advantage of ForkJoinPool is that it can make full use of the advantages of multi cpu and multi-core cpu to split a task into multiple "small tasks" and put multiple "small tasks" on multiple processor cores for parallel execution; When multiple "small tasks" are completed, these execution results can be combined.

 

Shutdown of thread pool

To close the thread pool, you can call the shutdown now and shutdown methods.

Shutdown now: issue interrupt() to all the tasks being executed, stop the execution, cancel all the tasks that have not been started, and return the list of tasks that have not been started.

Shutdown: when we call shutdown, the thread pool will no longer accept new tasks, but will not forcibly terminate the submitted or executing tasks.

 

Principle analysis of thread pool

The topmost implementation of the Executor framework is the ThreadPoolExecutor class. By passing in different parameters, you can construct thread pools suitable for different application scenarios.

Let's look at Java util. concurrent. The ThreadPoolExecutor class can see that the constructor of} ThreadPoolExecutor is as follows (line 1296):

    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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

Parameter introduction:

  1. corePoolSize: number of core threads. When the number of threads is less than this value, the thread pool will give priority to creating new threads to execute new tasks
  2. maximumPoolSize: the maximum number of threads, which is the maximum number of threads allowed to be created in the thread pool.
  3. keepAliveTime: the lifetime of threads created after exceeding the number of core threads.
  4. Unit: the time unit of keepAliveTime.
  5. workQueue: task queue, used to cache unexecuted tasks.
  6. threadFactory: the factory used to create threads in the thread pool. You can name threads through the factory class.
  7. handler: reject policy. When the thread pool and task queue are saturated, the reject policy is used to process new tasks. The default is AbortPolicy, that is, an exception is thrown directly

 

Deeply understand these concepts of thread pool:

1. corePoolSize: number of core threads. When a new task is submitted, first check the number of core threads. If the core threads are working and the number has reached the maximum number of threads, the new core thread will not be created, but the task will be placed in the work queue.

2. workQueue: task queue. It is used to store tasks that continue to be added when the core threads are busy. After executing the current task, the core thread will also pull the task from the task queue to continue execution. This task queue is generally a thread safe blocking queue, and its capacity can be customized by the developer.

3. maximumPoolSize: maximum number of threads. When the task queue is full, if the current number of threads does not exceed the maximum number of threads, a new thread will be created to pull tasks from the task queue for execution.

4. keepAliveTime: the lifetime of threads created after exceeding the number of core threads. If no task is executed after this time, the thread ends.

5. handler: reject policy. When the task queue is full and the number of threads reaches the maximum number of threads, the thread pool will perform subsequent operations according to the reject policy. The default policy is to reject the task and throw an exception.

Rejection policy table:

Implementation classexplain
AbortPolicyDiscard the new task and throw a RejectedExecutionException
DiscardPolicyDo nothing and discard the new task directly
DiscardOldestPolicyDiscard the element at the head of the queue and perform a new task
CallerRunsPolicyA new task is executed by the calling thread

 

How thread pools work

When a task needs to be executed, the thread pool first checks the number of core threads: if the number of threads in the thread pool is less than the number of core threads, a thread will be created to execute the task; If the number of thread pools is greater than the number of core threads, the task will be put into the work queue, and the task will be pulled for execution when the core thread is free; If the task fails to be added to the task queue (generally, the task queue is full), the thread pool will judge whether the current number of threads has reached the maximum poolsize. If not, create a new thread to execute the task; If the maximum number of threads is reached, the thread pool will perform subsequent operations according to the reject policy (handler).

Thread termination principle:

1. If the number of threads in the thread pool is greater than the number of core threads (corePoolSize) and keepAliveTime is set, idle threads exceeding the core threads will be terminated.

2. If the survival time of the core thread is set: allowCoreThreadTimeOut=true, the idle time of the thread in the core pool exceeds keepAliveTime, and the core thread will also be terminated.

 

I understand the concept of thread pool

The number of core threads is the regular army. When tasks come, give priority to them. The maximum number of threads is the maximum number that can be accommodated in the army. The army can hire some temporary soldiers. The difference between the maximum number of threads and the number of core threads is the number of temporary soldiers. There is also a limit on the number of task queues.

Process: when a task comes, let the regular army (core thread) execute it first. If the regular army is busy, put the task schedule into the task queue, and then the regular army circularly goes to the task queue to pull tasks for execution. If the task queue is full, the army will judge whether the maximum capacity is full. If not, it will hire temporary soldiers (create new tasks) to handle the task. If the task queue is full and the maximum capacity is full, the task will be rejected directly and will not be accepted.

 

Question: how do threads in the thread pool reuse?

A: after executing a task, the thread will not exit immediately, but will cycle to the task queue to pull the task for execution. If there are no tasks in the task queue and keepAliveTime is not set, the working thread will be blocked uniformly. In this way, thread reuse can be realized. If allowCoreThreadTimeOut=true is set, all threads in the thread pool will exit without getting the task after the keepAliveTime timeout.

 

How to reasonably set the thread pool size?

First, analyze the characteristics of the task from the following perspectives:

  1. Nature of task: CPU intensive task, IO intensive task and hybrid task. (CPU intensive means that the task requires a lot of computation without blocking, and the CPU runs at full speed all the time. IO intensive means that the task requires a lot of IO, that is, a lot of blocking. Running IO intensive tasks on a single thread will waste a lot of CPU computing power and wait.)
  2. Task priority: high, medium and low.
  3. Task execution time: long, medium and short.
  4. Task dependency: whether it depends on other system resources, such as database connection.

For tasks of different natures, CPU intensive tasks should be configured with as few threads as possible, such as configuring the number of CPUs + 1 threads, and IO intensive tasks should be configured with as many threads as possible, because IO operations do not occupy CPU, do not let CPU idle, and increase the number of threads, such as twice the number of CPUs + 1. For mixed tasks, if it can be split, Split it into IO intensive and CPU intensive types for processing respectively, provided that the running time of the two is the same. If the processing time is very different, there is no need to split it. If a task depends on other system resources, for example, a task depends on the results returned by the connection to the database. At this time, the longer the waiting time is, the longer the CPU is idle, and the larger the number of threads should be set to make better use of the CPU.

Optimal number of threads = ((thread waiting time + thread CPU time) / thread CPU time) × Number of CPUs

The higher the proportion of thread waiting time, the more threads are required. The higher the proportion of thread CPU time, the fewer threads are required.

 

Custom thread pool

Use # org springframework. scheduling. concurrent. ThreadPoolTaskExecutor

package com.test.util;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
 
/**
 * Thread pool configuration
 */
@Configuration
public class TaskPoolConfig {
 
    @Bean("myTaskExecutor")
    public Executor myTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);//Number of core threads, the number of threads initialized when the thread pool is created
        executor.setMaxPoolSize(15);//The maximum number of threads. Threads exceeding the number of core threads will be applied only after the buffer queue is full
        executor.setKeepAliveSeconds(60);//When the number of threads outside the core thread is exceeded, the thread will be destroyed after the idle time arrives
        executor.setQueueCapacity(200);//Buffer queue, which is used to buffer the queue for executing tasks
        executor.setThreadNamePrefix("myTask-");//After setting, it is convenient for us to locate the thread pool where the processing task is located
        executor.setWaitForTasksToCompleteOnShutdown(true);//It is used to set that when the thread pool is closed, wait until all tasks are completed, and then continue to destroy other beans
        executor.setAwaitTerminationSeconds(60);//This method is used to set the waiting time of tasks in the thread pool. If they are not destroyed after this time, they will be forcibly destroyed to ensure that the application can be closed instead of blocked.
        //Processing strategy of thread pool for rejected tasks: the CallerRunsPolicy policy is adopted here. When the thread pool has no processing capacity, this policy will directly run the rejected tasks in the calling thread of execute method; If the executor is closed, the task is discarded
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

 

 

Future is an interface for canceling the execution result of a specific Runnable or Callable task, querying whether it is completed, and obtaining the result. If necessary, you can get the execution result through the get method, which will block until the task returns the result.

If you also want to study Future and Callable, you can check the blog: https://blog.csdn.net/BiandanLoveyou/article/details/83586356

 

OK, that's all for thread pool.

 

 

Topics: jvm thread pool