Breakdown thread pool test questions: 3 methods, 7 parameters and 4 rejection strategies

Posted by WakeAngel on Thu, 25 Nov 2021 04:58:20 +0100

Multithreading knowledge is a must in Java interview. This article introduces thread pool in detail. In the actual development process, many IT practitioners have a low utilization rate. They just understand a theoretical knowledge and recite various eight part essays, but they don't deeply understand them in their mind, resulting in forgetting after the interview—— Code = knock code; Programmer = understanding

Thread pool interview must test points: 3 methods, 7 parameters and 4 rejection strategies!

catalogue

▶ introduce

1, Thread Pool

2, Executor, Executors and ExecutorService, don't be silly and confused!

▶   Punch through one by one

1, 3 major methods

2, 7 major parameters

3, 4 rejection strategies

▶   last

▶ introduce

1, Thread Pool

The essence of program operation is to occupy system resources! The competition of resources will affect the operation of the program, which is bound to optimize the utilization of resources. For example: the birth of pooling technology! Common are: object pool, memory pool, jdbc connection pool, thread pool and so on

Principle of pooling Technology: prepare resources in advance. If someone wants to use them, come to me and give them back to me after use!

We know that creating and destroying threads is a waste of resources, which not only generates additional overhead, but also reduces the computer performance. Thread pool is used to manage threads. The vernacular summary is that threads can be reused, the maximum number of concurrent threads can be controlled, and threads are easy to manage

  • Reduce the overhead of frequent thread creation and destruction!
  • Avoid excessive cpu scheduling caused by too many threads!
  • Improved response speed!

Resources are managed together to maximize benefits and minimize risks

 

2, Executor, Executors and ExecutorService, don't be silly and confused!

Executors   Factory tools    

Is a tool class that provides factory methods to create different types of thread pools for everyone to use!

What kind of thread pool you want is new, which is equivalent to a factory!!

Common thread pool types provided by the factory:

// Cacheable thread pool: worker threads will be recycled if they are idle for 60 seconds. If a new task is added after termination, a new thread will be created
Executors.newCachedThreadPool(); 

// Fixed thread pool: if the number of working threads is less than the number of core threads, a new thread is created to execute tasks; Worker threads = number of core threads, and new tasks are put into the blocking queue
Executors.newFixedThreadPool(); 

// Single thread pool: only one thread executes tasks in the specified order (FIFO,LIFO), and the newly added tasks are placed in the blocking queue (serial)
Executors.newSingleThreadExecutor(); 
  
// Timed thread pool: supports timed and periodic task execution
Executors.newScheduledThreadPool(); 

  Executor   Performer interface    

The top-level interface of thread pool. Other classes implement it directly or indirectly! Only one execute() method is provided to execute the submitted Runnable task -- just a tool class for executing threads!!

public interface Executor {
    void execute(Runnable command);
}

ExecutorService   Thread pool interface  

The thread pool interface inherits the Executor and extends the Executor [Executor interface]. It can close the thread pool, submit threads, obtain execution results, and control the execution of threads. It can be understood as: the upgraded version of the Executor interface Executor!

public interface ExecutorService extends Executor {
    //Gently terminate the thread pool, no longer accept new tasks, and still process the tasks submitted to the thread pool
    void shutdown();

    //Strongly terminate the thread pool, no longer accept new tasks, give up the submitted and unprocessed tasks, and return
    List<Runnable> shutdownNow();

    //Judge whether the current thread pool state is non running, and shutdown() or shutdown now() has been executed
    boolean isShutdown();

    //Has the thread pool terminated
    boolean isTerminated();

    //Block the current thread, wait for the thread pool to terminate, and support timeout
    boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException;
    ...
}

Extension: the following table lists the differences between Executor and ExecutorService

ExecutorExecutorService
The core interface of Java thread pool is used to execute submitted tasks concurrentlyIt is an extended sub interface of the Executor interface and provides methods for asynchronous execution and closing the thread pool
The execute() method is provided to submit the taskThe submit() method is provided to submit the task
No return valueThe submit() method returns the Future object, which can be used to obtain the task execution result
You cannot cancel the taskYou can cancel tasks in pending through Future.cancel()
Closing thread pools is not supportedProvides a method to close the thread pool

▶   Punch through one by one

1, 3 major methods

Refers to the three thread pools provided by the Executors factory class.

In the above explanation, the Executors factory class describes four creation methods, and only three of the most tested are shown here!

a. Single pass pool  

public static void main(String[] args) {
     // 1. Define a single thread pool: there is only one thread in the pool
     ExecutorService pool = Executors.newSingleThreadExecutor(); 

     // 2. Define 5 threads
     for (int i = 0; i < 5; i++) {
          pool.execute(()->{
               System.out.println(Thread.currentThread().getName()+" hi");
          });
     }

     // 3. Close thread pool
     pool.shutdown();
}
Execution result: there is only one thread from beginning to end and five tasks are executed

 

b. Cacheable thread pool  

public static void main(String[] args) {
        /** Note: the maximum number of threads created in the pool is Integer.MAX_VALUE (approximately 2.1 billion)
                 If a thread fails to execute a task for more than 60 seconds, it will automatically recycle the thread and translate it into a cacheable pool */
        // 1. Define a cacheable thread pool
        ExecutorService pool =Executors.newCachedThreadPool(); 

        // 2. Define 5 threads
        for (int i = 0; i < 5; i++) {
            pool.execute(()->{
                System.out.println(Thread.currentThread().getName()+" hi");
            });
        }

        // 3. Close thread pool
        pool.shutdown();
}

Execution result: five threads are created in the pool to execute five tasks. The maximum number of threads can be created depends on your computer performance

c. Fixed thread pool  

public static void main(String[] args) {
   
        // 1. Define a thread pool with a fixed length of 3
        ExecutorService pool =Executors.newFixedThreadPool(3); 

        // 2. Define 5 threads
        for (int i = 0; i < 5; i++) {
            pool.execute(()->{
                System.out.println(Thread.currentThread().getName()+" hi");
            });
        }

        // 3. Close thread pool
        pool.shutdown();
}

Execution result: three threads are defined in the pool, so there are only three threads executing tasks at most

Test point analysis: why is thread pool not allowed to use Executors factory class to create!!

A: avoid the risk of resource depletion. The disadvantages are as follows: (if you can't understand it, see the explanation of the following 7 parameters)

  • FixedThreadPool and SingleThreadPool: the task capacity of the blocking queue is Integer.MAX_VALUE (about 2.1 billion) will accumulate a large number of requests, resulting in OOM and system paralysis.

  • CachedThreadPool and ScheduledThreadPool: the maximum number of threads created is Integer.MAX_VALUE, creating a large number of threads is easy to cause OOM.

Fast memory: fixed or single length thread pool, unlimited queue capacity! Non fixed pool, no limit on the number of threads created!


 

2, 7 major parameters

It refers to the 7 setting parameters (key points) in the custom thread pool

Key points: first, let's take a look at the source code analysis of the three methods provided by the Executors factory class

// Fixed length thread pool: nThreads is the incoming parameter. The maximum number of threads and the number of core threads are both this value. The length of the number of fixed threads is
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

// Single thread pool: only one thread executes because the number of core threads and the maximum number of threads are 1
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));

// Cacheable thread pool: there is no limit on the maximum number of threads created. An idle time of 60 seconds is set. Idle threads beyond this time will be recycled
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}

We can see that in the above three methods, the same new ThreadPoolExecutor() is actually used to create the thread pool at the bottom. ThreadPoolExecutor is a kind of thread pool tool provided by JUC. Literally, it refers to the resource pool that manages a group of homogeneous working threads. It is generally understood as a custom thread pool.

Let's take a look at the construction parameters of this class:

/** 
      corePoolSize: Number of core threads
   maximumPoolSize: Maximum number of threads that can be created
     keepAliveTime: Survival time. Threads that do not execute tasks beyond this time will be recycled
              unit: Unit of survival time
     BlockingQueue: Blocking queue
     ThreadFactory: Thread factory is used to create threads. Generally, it is not used
   RejectedExecutionHandler: Reject policy
*/
public ThreadPoolExecutor(int corePoolSize, 
                          int maximumPoolSize, 
                          long keepAliveTime, 
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory, 
                          RejectedExecutionHandler handler) {
       ...
}

corePoolSize number of core threads

  • By default, the core thread is created and started only when a new task arrives, which is available   prestartCoreThread() starts a core thread   and   prestartAllCoreThreads()   The method of starting all core threads is dynamically adjusted
  • Even if there is no task execution, the core thread will always survive
  • When the number of threads in the pool is less than the core thread, the thread pool will create a new core thread to execute tasks even if there are idle threads
  • When allowCoreThreadTimeout=true is set, the core thread will close over time

maximumPoolSize maximum number of threads

  • When all core threads are executing tasks and the task queue is full, the thread pool will create new non core threads to execute tasks
  • When the number of threads in the pool = the maximum number of threads and the queue is full, a new task will trigger the RejectedExecutionHandler policy

keepAliveTime TimeUnit thread idle time

  • If the number of threads > the number of core threads, when the idle time of the thread reaches keepAliveTime, the thread will be recycled and destroyed until the number of threads = core threads
  • If allowCoreThreadTimeout=true, the core thread will also be destroyed after executing the task until the quantity = 0

workQueue task queue

  • For a description of the queue, refer to:

ThreadFactory creates a factory for threads

  • It is generally used to customize the thread name. When there are many threads, it can be grouped and identified

RejectedExecutionHandler reject policy

  • When the maximum number of threads and queue are full, please refer to the following four strategies for processing new tasks

The description of the above parameters is too theoretical. I will use life examples to illustrate the use of key parameters

Simulate bank business process

  1. The bank is equivalent to a thread pool
  2. There are at most five windows in the bank that can handle business (maximumPoolSize maximum number of threads created), only the first two windows are processing business (number of corePoolSize core threads), and the other three windows are suspended
  3. There are only 3 places in the waiting area for queuing (the queue capacity of workQueue is 3), and it is full of people

The thread pool parameters obtained from the figure are:

  • corePoolSize number of core threads: 2
  • maximumPoolSize maximum threads: 5
  • workQueue task queue size: 3

Conclusion: if the core window is full, the new business person will enter the queue waiting area to wait (blocking queue)

Thinking: the core window and waiting area are full. What if two new business people come into the bank at this time?

  It can be seen from the figure that the process of the two new employees is as follows:

  1. If the core window is occupied (core thread), judge whether the waiting area (blocking queue) is full
  2. If the queue number area is not full, judge whether the waiting area (blocking queue) still has a place. If so, enter the waiting area and wait in line.
  3. If the number queuing area and the core window are full (number of core threads + capacity of blocking queue), judge whether all the bank windows (maximum number of thread creation) are handling business. If not, open two new windows to handle business for two new people (non core Windows)

Conclusion: the number of core threads 2 + blocking queue 3 are all full. If the maximum number of working threads has not been reached, the thread pool will open and create two new non core Windows for new tasks

Thinking: what if the bank has two more people handling business at this time?

As can be seen from the figure:

  1. Window 5 is not full, so it will be opened to one of the new comers for business
  2. Another person will be rejected because all five windows are occupied and the waiting area is full (rejection strategy)

In summary, the operation principle of thread pool is as follows:

  1. When the core thread is not full, even if the threads in the pool are idle, a new core thread is created to process new tasks.

  2. If the number of core threads is full, but the blocking queue workQueue is not full, the new task will be put into the queue.

  3. The number of core threads and the blocking queue are full. If the current number of threads is less than the maximum number of threads created, new (non core) threads will be created to process new tasks

  4. If all three are full (number of core threads, blocking queue and maximum number of threads), the task is processed through the specified rejection policy

The priority is: core thread corePoolSize, task queue workQueue, (non core thread) maximum thread maximumPoolSize. If all three are full, the handler rejection strategy is adopted


3, 4 rejection strategies

It refers to the processing method of new tasks when the maximum number of threads in the thread pool and the queue are full

  • CallerRunsPolicy (caller running policy): use the thread that is currently invoked (the thread that submits the task) to perform this task.

  • AbortPolicy: reject and throw an exception (default)

  • Discard policy: discard this task without throwing exceptions

  • Discard oldest policy: discards a task in the queue head (oldest) and executes the current task

I'll use code to demonstrate:

  CallerRunsPolicy (caller run policy)

/**
      corePoolSize Number of core threads: 2
   maximumPoolSize Maximum threads: 3
       workQueue Blocking queue capacity: 2
    RejectedHandler Reject policy: CallerRunsPolicy caller run
*/
public static void main(String[] args) {
        // 1. Customize a thread pool
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 3, 0
                , TimeUnit.SECONDS
                ,new LinkedBlockingQueue<Runnable>(2)
                , new ThreadPoolExecutor.CallerRunsPolicy()); // Specify reject policy

        // 2. Submit 6 tasks
        for (int i = 0; i < 6; i++) {
            final int num =i;
            pool.execute(()->{
                System.out.println(Thread.currentThread().getName()+" ok: "+num);
            });
        }

        // 3. Close thread pool
        pool.shutdown();
    }

The results are as follows:

Note: first, six tasks are submitted, and the thread capacity that the thread pool can receive is: queue 2 + maximum number of threads created 3 = 5. Because the maximum number of threads created is 3, there are only three threads at most to poll and execute five tasks. For the redundant sixth task, the thread pool cannot run because it is full. The rejection policy specified by the thread pool is: caller run policy   That is, the thread submitting the task handles it, that is, the main thread

AbortPolicy

Consistent with the above caller policy code, you can modify the specific policy type behind ThreadPoolExecutor

new ThreadPoolExecutor.AbortPolicy(); // Specify reject policy

The results are as follows:

Note: because the abort strategy is adopted, the task is rejected and an exception is thrown. The sixth task cannot be executed because the thread pool is full.

Discard policy

new ThreadPoolExecutor.DiscardPolicy(); // Specify reject policy

The results are as follows:

  Note: the sixth task is discarded and the program is finished

DiscardOldestPolicy

new ThreadPoolExecutor.DiscardOldestPolicy(); // Specify reject policy

The results are as follows:

Note: first, there are two core threads, so tasks 1 and 2 directly enter the thread pool and are executed by the core thread. When task 3 comes in, if the core thread is full, it enters the queue for waiting, and task 4 then enters the queue. When task 5 comes in, because the core thread and queue are full, but the maximum number of threads created has not been reached 3, a non core thread will be created to process task 5. At this time, the maximum number of threads in the pool has been reached, and the queue is full. Finally, task 6 comes in, so task 3 that entered the queue first will be discarded

▶   last

  1. This article does not explain a lot of source code knowledge in depth, but with the reader's understanding: what is a thread pool? How to use it?
  2. The essential knowledge of the interview is basically around: 3 methods, 7 parameters and 4 rejection strategies   To ask questions, if you can read this article, you have basically mastered the knowledge of thread pool!
  3. It's not shameful to screw up at work! It's shameful to memorize eight part essays by rote. Only by truly understanding the knowledge in your mind can you gain something. Otherwise, every article you read is just someone else's notes. Recite before the interview, forget after the interview, resolutely refuse short-term memory, and understand it in my own way. This is the point I want to express!

Good judgment comes from experience, but experience comes from wrong judgment.

Topics: Java Concurrent Programming thread pool