Five states of thread pool and several ways to create thread pool

Insert picture description here

Foreword of the previous article " Detailed Explanation of the 6 States of Java Threads and 4 Ways to Create Threads " : We all know that threads are rare resources, and frequent system creation will greatly affect the efficiency of the server. If you do not limit it, it is easy Will run out of server resources. Therefore, we can manage these threads by creating a thread pool and increase the utilization rate of threads.

1. What is a thread pool?

In short, the thread pool is a container for managing threads. When there is a task to be processed, it will successively determine whether the number of core threads is still free, whether the task queue in the thread pool is full, whether it exceeds the size of the thread pool , and then call or Create a thread or queue, the thread will not be destroyed immediately after executing the task, but will still wait for the next task in the thread pool. If there is no new task beyond the survival time, it will be destroyed. By reusing threads in this way, the reduction is reduced Overhead.

2. What are the advantages of using thread pools?

Some people may ask, are there any benefits to using thread pools? Needless to say, the benefits are naturally there. There are probably the following:
1. Increase the usage rate of threads in the thread pool and reduce the creation and destruction of objects.
2. The scalability of the thread pool has a greater impact on performance. The thread pool can be used to control the number of threads, effectively improve the server's resources, and avoid downtime due to insufficient resources. ( Creating too many threads will waste certain resources, and some threads will not be fully used; destroying too many threads will result in wasting time to create them again; creating threads too slow will lead to long waits and poor performance ; Destroying threads is too slow, leading to starvation of other threads. )

3. The core workflow of the thread pool (important)

If we want to use the thread pool, we must first understand how it works. The process is as shown in the figure below. Just look at the picture without much nonsense. The core is to reuse threads and reduce overhead.
Thread pool workflow

4. The five-state life cycle of the thread pool

  • RUNNING: Can accept newly submitted tasks, and can also process tasks in the blocking queue.
  • SHUTDOWN: Closed state, no longer accepting newly submitted tasks, but can continue to process the saved tasks in the blocking queue. When the thread pool is in the RUNNING state, calling the shutdown() method will make the thread pool enter this state. (The finalize() method will also call the shutdown() method to enter this state during execution).
  • STOP: Can not accept new tasks, do not process tasks in the queue, and interrupt the thread that is processing tasks. When the thread pool is in the RUNNING or SHUTDOWN state, calling the shutdownNow() method will cause the thread pool to enter this state.
  • TIDYING: If all tasks have been terminated, workerCount (the number of effective threads) is 0, and the thread pool will call the terminated() method to enter the TERMINATED state after entering this state.
  • TERMINATED: Enter this state after the terminated() method is executed. By default, nothing is done in the terminated() method.
    Life cycle flow chart of thread pool

    5. Several ways to create thread pools

  • Created by Executors factory method
  • Customize creation through new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue).
    Relatively speaking, it is recommended to use the second to create a thread pool. The thread pool created by Executors is used in many places* **Task queue, in high concurrency scenarios, the **task queue will receive too many task objects, which will cause the JVM to crash in severe cases. Some large factories also prohibit the use of Executors factory methods to create thread pools. The main problem of newFixedThreadPool and newSingleThreadExecutor is that the accumulated request processing queue may consume very large memory, or even OOM; the main problem of newCachedThreadPool and newScheduledThreadPool is that the maximum number of threads is Integer.MAX_VALUE, which may create a very large number of threads, or even OOM .

    5.1. The difference between the five factory methods of Executors to create different thread pools

    Insert picture description here
    1. newCachedThreadPool() (the work queue uses SynchronousQueue)
    creates a thread pool. If the number of threads in the thread pool is too large, it can effectively reclaim the excess threads. If the number of threads is insufficient, then it can create new threads.
    Disadvantage: Although this method can automatically expand the number of threads to process our business according to the business scenario, we cannot control how many threads are required to process at the same time.
    Advantages: If when the second task starts, the execution of the first task has ended, then the second task will reuse the thread created by the first task, and will not recreate a new thread, which improves the thread reuse rate .
    Function: This method returns a thread pool that can adjust the number of threads in the thread pool according to the actual situation. That is, the number of threads in the thread pool is uncertain and dynamically adjusted according to actual conditions.
    2. newFixedThreadPool() (Work queue uses LinkedBlockingQueue)
    This way you can specify the number of threads in the thread pool. If a new task comes after it is full, you can only wait in line at this time.
    Advantages: the number of threads of newFixedThreadPool can be controlled, so we can maximize the utilization of our server by controlling the maximum thread, and at the same time we can ensure that even if the traffic increases suddenly, it will not occupy too much server resources.
    Function: This method returns a thread pool with a fixed number of threads, the number of threads in the thread pool is always the same, that is, no new threads will be created, and threads that have been created will not be destroyed. Several fixed threads are working, so the thread pool can control the maximum concurrent number of threads.
    3. newScheduledThreadPool()
    This thread pool supports timing and periodic task execution. We can delay the execution time of the task, or set a periodic time for the task to be executed repeatedly. There are two delay methods in this thread pool.
    The difference of scheduleAtFixedRate is the execution time of the task. If the interval time is greater than the execution time of the task, the task is not affected by the execution time. If the interval time is less than the execution time of the task, it will be executed immediately after the task execution ends, and the interval time will be disrupted.
    The interval of scheduleWithFixedDelay will not be affected by the task execution time.
    Function: This method returns a thread pool that can control the timing or periodic execution of a task in the thread pool.
    4. newSingleThreadExecutor()
    This is a single-threaded pool, which is executed by one thread from beginning to end.
    Function: This method returns a thread pool with only one thread, that is, only one thread task can be executed at a time, and the excess tasks will be saved in a task queue, waiting for this thread to be free, and when the thread is free, then follow the FIFO order Execute tasks in the task queue.
    5. newSingleThreadScheduledExecutor()
    has only one thread, which is used to schedule tasks to be executed at a specified time.
    Function: This method returns a thread pool that can control the timing or periodic execution of a task in the thread pool. The only difference from the above is that the thread pool size is 1, and the above can specify the size of the thread pool.
    Use example:

    //创建一个会根据需要创建新线程的线程池
    ExecutorService executor= Executors.newCachedThreadPool();
    for (int i = 0; i < 20; i++) {
    executor.submit(new Runnable() {
        @Override
        public void run() {
            System.out.println(i);
        }
    });
    }

    These five thread pools are all directly or indirectly obtained ThreadPoolExecutor instances, but the parameters passed during instantiation are different. So if the thread pool provided by Java cannot meet our needs, we can create a custom thread pool through the ThreadPoolExecutor construction method.

    5.2 Detailed explanation of the parameters of the ThreadPoolExecutor construction method

public ThreadPoolExecutor(
int corePoolSize,//线程池核心线程大小
int maximumPoolSize,//线程池最大线程数量
long keepAliveTime,//空闲线程存活时间
TimeUnit unit,//空闲线程存活时间单位,一共有七种静态属性(TimeUnit.DAYS天,TimeUnit.HOURS小时,TimeUnit.MINUTES分钟,TimeUnit.SECONDS秒,TimeUnit.MILLISECONDS毫秒,TimeUnit.MICROSECONDS微妙,TimeUnit.NANOSECONDS纳秒)
BlockingQueue<Runnable> workQueue,//工作队列
ThreadFactory threadFactory,//线程工厂,主要用来创建线程(默认的工厂方法是:Executors.defaultThreadFactory()对线程进行安全检查并命名)
RejectedExecutionHandler handler//拒绝策略(默认是:ThreadPoolExecutor.AbortPolicy不执行并抛出异常)
) 

Use example:

ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));

5.2.1, work queue

Four kinds of work queues are provided in jdk:
①ArrayBlockingQueue is
an array-based bounded blocking queue, sorted by FIFO. When new tasks come in, they will be placed at the end of the queue. A bounded array can prevent resource exhaustion. When the number of threads in the thread pool reaches corePoolSize, and a new task comes in, the task will be placed at the end of the queue, waiting to be scheduled. If the queue is full, a new thread is created, and if the number of threads has reached maxPoolSize, the rejection strategy will be executed.
②LinkedBlockingQuene is
based on the *** blocking queue of the linked list (in fact, the maximum capacity is Interger.MAX_VALUE), sorted by FIFO. Due to the approximate nature of the queue, when the number of threads in the thread pool reaches corePoolSize, new tasks will always be stored in the queue instead of creating new threads until maxPoolSize. Therefore, when using the work queue, The parameter maxPoolSize actually has no effect.
③SynchronousQuene is
a blocking queue that does not cache tasks. The producer puts a task into it and must wait until the consumer takes it out. That is to say, when a new task comes in, it will not be cached, but will be directly scheduled to execute the task. If there is no available thread, a new thread will be created. If the number of threads reaches maxPoolSize, the rejection strategy will be executed.
④PriorityBlockingQueue
has a priority *** blocking queue, and the priority is achieved through the parameter Comparator.

5.2.2, rejection strategy

When the tasks in the work queue have reached the maximum limit, and the number of threads in the thread pool has also reached the maximum limit, at this time, if a new task is submitted, the rejection strategy will be executed . Four rejection strategies are provided in jdk:
①ThreadPoolExecutor.CallerRunsPolicy Under
this strategy, 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 abandoned.
② ThreadPoolExecutor.AbortPolicy under
this strategy, directly discard the task, and throw a RejectedExecutionException exception.
③ ThreadPoolExecutor.DiscardPolicy under
this strategy, directly discard the task, do nothing.
④ ThreadPoolExecutor.DiscardOldestPolicy under
this strategy, discard the earliest task that entered the queue, and then try to put the rejected task into the queue.
In addition, you can also implement a custom strategy for the RejectedExecutionHandler interface according to the needs of the application scenario.

6. Closing the thread pool

  • shutdown():
    1. It is not allowed to continue adding threads to the thread pool after the call;
    2. The state of the thread pool changes to the SHUTDOWN state;
    3. All tasks submitted to ExecutorSrvice before the shutdown() method is called will be executed;
    4. Once all When the thread finishes executing the current task, the ExecutorService will be shut down.
  • shutdownNow():
    1. This method returns a list of tasks that have not been executed;
    2. The state of the thread pool changes to the STOP state;
    3. Try to stop all threads that are executing or suspending tasks.
    To put it simply, it is: After
    shutdown() is called, no new tasks can be submitted. Those that have been submitted will continue to execute
    shutdownNow(). After calling shutdownNow(), try to stop the currently executing task and return to the list of tasks that have not yet been executed.

    7. Summary

    This article briefly introduces some relevant knowledge of thread pools. I believe that everyone has a general understanding of the advantages of thread pools, the life cycle of thread pools, the work flow of thread pools and the use of thread pools, and I hope that people in need Provide a little help! There are errors in the article, please leave a message for corrections, thank you~
    and welcome everyone to pay attention to my official account: Java's road to god, free to receive the latest interview materials, technical e-books, advanced architecture related materials, etc.
    Insert picture description here

Guess you like

Origin blog.51cto.com/14956044/2545532