JUC series | ThreadPool thread pool

Keywords: Multithreading thread pool JUC threadpool

Multithreading has always been a difficulty in Java development and a frequent visitor in the interview. While there is still time, I intend to consolidate my knowledge of JUC. I think opportunities can be seen everywhere, but they are always reserved for those who are prepared. I hope we can all refuel!!!

Sink and float up. I think we will be different.

🍟 Introduction to thread pool

1) What is a thread pool?

thread pool: a thread usage mode. The container maintained by the system to hold threads is shared by all AppDomain s controlled by the CLR. Thread pools can be used to perform tasks, send work items, process asynchronous I/O, wait on behalf of other threads, and process timers.

2) Why use thread pools?

Pain points:

  1. If the thread pool is not used, a new thread will be created for each request and then destroyed, resulting in high resource consumption and low reuse rate.
  2. In Java, the memory occupied by the thread stack of a thread is outside the Java heap and is not controlled by the Java program. It is only limited by the system resources (if the system gives insufficient resources, the creation may fail). By default, the thread stack size of a thread is 1M. If a new thread is created for each request, 1024 threads will occupy 1 G of memory, and the system is easy to crash.
  3. If the system is huge, new threads are still created in the code every time, which is difficult to manage, find errors, monitor and tune.
  4. Too many threads will bring scheduling overhead, which will affect cache locality and overall performance.

reason:

The thread pool maintains multiple threads, waiting for the supervisor to assign concurrent tasks. This avoids the cost of creating and destroying threads when processing short-time tasks. Thread pool can not only ensure the full utilization of the kernel, but also prevent over scheduling.

Advantages of thread pool:

As long as the work done by the thread pool is to control the number of running threads, put the tasks in the queue during processing, and then start these tasks after the threads are created. If the number of threads exceeds the maximum number, the threads exceeding the number will queue up and wait until other threads have finished executing, and then take the tasks out of the queue for execution.

In addition, the Alibaba Java development manual we often read also mentioned:

[mandatory] thread resources must be provided through the thread pool. It is not allowed to create threads explicitly in the application.

Note: the advantage of using thread pool is to reduce the time spent on creating and destroying threads and the overhead of system resources, and solve the problem of insufficient resources. If the thread pool is not used, the system may create a large number of similar threads, resulting in memory consumption or "excessive switching".

3) Characteristics

Reduce resource consumption: reduce the consumption caused by thread creation and destruction by reusing the created threads.

Improve response speed: when the task arrives, the task can be executed immediately without waiting for thread creation.

Improve the manageability of threads: 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 carry out unified allocation, tuning and monitoring.

4) Structure

The thread pool in Java is implemented through the Executor framework, which uses Executor, ExecutorService, ThreadPoolExecutor and other classes

In addition, Executors is a convenient tool class for encapsulation. However, it is not recommended 😂

5) Thread pool parameter description

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  1. corePoolSize the number of core threads in the thread pool
    • When submitting a task, if the number of threads in the current core thread pool does not reach corePoolSize, a new thread will be created to execute the submitted task, even if there are idle threads in the current core thread pool. If the number of threads in the current core thread pool has reached corePoolSize, the threads will not be re created.
  2. maximumPoolSize the maximum number of threads that can be accommodated
    • That is, when the blocking queue is full and the number of threads in the current thread pool does not exceed maximumPoolSize, a new thread will be created to execute the task.
  3. keepAliveTime idle thread lifetime
    • If the number of threads in the current thread pool has exceeded corePoolSize and the idle time of threads has exceeded keepAliveTime, these idle threads will be destroyed, which can reduce the system resource consumption as much as possible.
  4. Unit is the time unit of survival
  5. workQueue holds the queue that submitted but did not execute the task, that is, the blocking queue.
  6. threadFactory creates a factory class for threads
  7. handler reject policy when waiting queue is full

In the thread pool, there are three important parameters that affect the rejection policy:

  1. corePoolSize - number of core threads, that is, the minimum number of threads.
  2. workQueue - block the queue.
  3. maximumPoolSize - maximum number of threads. When the number of submitted tasks is greater than corePoolSize, tasks will be put into the workQueue blocking queue first. When the blocking queue is saturated, the number of threads in the thread pool will be expanded until it reaches the maximum poolsize configuration. At this time, if there are more redundant tasks, the rejection policy of the thread pool will be triggered. To sum up, in a word, when the number of tasks submitted is greater than (workQueue.size() + maximumPoolSize), the rejection policy of the thread pool will be triggered.

6) Reject policy

  1. AbortPolicy: discards the task and throws an exception message rejecting the execution of RejectedExecutionException. Thread pool default reject policy. The exceptions thrown must be handled properly, otherwise the current execution process will be interrupted and subsequent task execution will be affected.

  2. CallerRunsPolicy: when the reject policy is triggered, as long as the thread pool is not closed, the calling thread will be used to run the task directly. Generally, concurrency is relatively small, performance requirements are not high, and failure is not allowed. However, because the caller runs the task himself, if the task submission speed is too fast, it may lead to program blocking and inevitable loss of performance and efficiency

  3. DiscardPolicy: discard directly

  4. DiscardOldestPolicy: when the rejection policy is triggered, as long as the thread pool is not closed, discard the oldest task in the blocking queue workQueue and add a new task

🧇 Thread pool type

Jav has four default thread pools:

2.1, newCachedThreadPool

Function: create a cacheable thread pool. This thread pool does not limit the size of the thread pool. The size of the thread pool completely depends on the maximum thread size that the operating system (or JVM) can create. If the length of thread pool exceeds the processing requirements, idle threads can be recycled flexibly. If there is no recyclable thread, a new thread can be created

It is more suitable for creating an infinitely scalable thread pool with short execution time and many tasks.

characteristic:

  1. The number of threads in the thread pool is not fixed and can reach (Interger. MAX_VALUE)
  2. Threads in the thread pool can be reused and recycled (the default recycling time is 1 minute, that is, threads without tasks within 1 minute).

Creation method:

/**
     * Cacheable thread pool
     * @return
     */
public static ExecutorService newCachedThreadPool() {
    /**
        * corePoolSize Number of core threads in the thread pool
        * maximumPoolSize Maximum number of threads that can be accommodated
        * keepAliveTime Idle thread lifetime
        * unit Time unit of survival
        * workQueue A queue that holds submitted but not executed tasks
        * threadFactory Factory class for creating thread: can be omitted
        * handler Reject policy after waiting queue is full: can be omitted
        */
    return new ThreadPoolExecutor(0,
                                  Integer.MAX_VALUE,
                                  60L,
                                  TimeUnit.SECONDS,
                                  new SynchronousQueue<>(),
                                  Executors.defaultThreadFactory(),
                                  new ThreadPoolExecutor.AbortPolicy());
}

2.2,newFixedThreadPool

effect:

It is used to create a reusable thread pool with a fixed number of threads and run these threads in the form of a shared unbounded queue.

It is suitable for limiting the number of threads when predicting concurrency pressure; Or there are strict restrictions on the number of threads.

characteristic:

  1. The number of threads is fixed and reusable.
  2. The corePoolSize and maximunPoolSize settings are equal.
  3. keepAliveTime is 0. Once there are redundant idle threads, they will be stopped immediately.
  4. The blocking queue adopts LinkedBlockingQueue, which is an unbounded queue, so it is never possible to reject tasks, and excess tasks need to wait in the queue.

Creation method:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

2.3,newSingleThreadExecutor

effect:

Create an Executor that uses a single worker thread to run the thread as a linked blocking queue. The exceeded tasks will be stored in the queue and waiting for execution.

It is applicable to the scenario where each task needs to be executed in sequence and there will be no multiple threads at any point in time

characteristic:

At most one thread can be executed in the thread pool, and then the thread activities submitted will be queued for execution

Creation method:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

2.4,newScheduledThreadPool

effect:

Create a thread pool with corePoolSize as the incoming parameter and the maximum number of threads (Integer.MAX_VALUE). This thread pool supports the requirements of regular and periodic task execution.

It is more suitable for scenarios where multiple background threads are required to execute periodic tasks. It is appropriate to collect logs at some time, send push messages, pull messages, etc 😂

characteristic:

Thread activity can be executed regularly or delayed ~ ~ (I used this to simulate dynamic timing, which means that given a future time, it will be executed at that time point)~~

Creation method:

public static ScheduledExecutorService newScheduledThreadPool(
    int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
    
public ScheduledThreadPoolExecutor(int corePoolSize,
                                   ThreadFactory threadFactory) {
    super(corePoolSize, Integer.MAX_VALUE,
          DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
          new DelayedWorkQueue(), threadFactory);
}

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

🍖 Introductory case

There are 3 ticket outlets in the railway station and 10 users buy tickets

import java.util.concurrent.*;

/**
 * Introductory case
 */
public class ThreadPoolDemo1 {


    /**
     * There are 3 ticket outlets in the railway station and 10 users buy tickets
     *
     * @param args
     */
    public static void main(String[] args) {
        //Timed thread times: the number of threads is 3 --- the number of windows is 3
        ExecutorService threadService = new ThreadPoolExecutor(3,
                3,
                60L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        try {
            //Ten people buy tickets
            for (int i = 1; i <= 10; i++) {
                threadService.execute(() -> {
                    try {
                        System.out.println(Thread.currentThread().getName() + " window,Start selling tickets");
                        Thread.sleep(500);
                        System.out.println(Thread.currentThread().getName() + " End of window ticket buying");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //End after completion
            threadService.shutdown();
        }
    }
}
/**
 pool-1-thread-3 Window, start selling tickets
 pool-1-thread-2 Window, start selling tickets
 pool-1-thread-1 Window, start selling tickets
 pool-1-thread-3 End of window ticket buying
 pool-1-thread-2 End of window ticket buying
 pool-1-thread-1 End of window ticket buying
 ....
 */

🍤 Working principle of thread pool (important)

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
   //If the number of threads in the thread pool is less than corePoolSize, a new thread is created to execute the current task
    if (workerCountOf(c) < corePoolSize) {
        //Execute addworker to create a core thread. If the creation fails, obtain the ctl again
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
//If the number of worker threads is greater than the number of core threads, judge whether the status of the thread pool is running and can be added to the queue
//If the thread pool is not in the running state, the reject policy will be executed (addworker will still be called once)
    if (isRunning(c) && workQueue.offer(command)) {
         //Obtain ctl again for double retrieval
        int recheck = ctl.get();
        //If the thread pool is in the RUNNING state, the task will be removed from the queue, 
        //If the removal fails, it will judge whether the worker thread is 0. If it is too 0, a non core thread will be created 
        //If the removal is successful, the reject policy is executed because the thread pool is no longer available;
        if (! isRunning(recheck) && remove(command))
            //Call the rejected execution handler for the given command
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
   //Checks whether new worker threads can be added based on the current pool state and given bounds (core or maximum). 
   //If so, adjust the number of staff accordingly, and if possible, create and start a new staff member, running firstTask as its first task. 
   //This method returns false if the pool is stopped or eligible for shutdown. 
   //If the thread factory fails to create a thread when asked, it also returns false. 
   //If the thread creation fails, either because the thread factory returns null or because of an exception (usually OutOfMemoryError in Thread.start()), we will roll back cleanly.
    else if (!addWorker(command, false))
        // Call the rejected execution handler for the given command
        reject(command);
}

The above figure is taken from: Implementation principle of thread pool ThreadPoolExecutor Author: listen___

execute execution logic:

  1. After the thread pool is created, the number of threads in the thread pool is zero
  2. When calling the execute() method to add a request task, the thread pool will make the following judgment:
    • If the number of running threads is less than corePoolSize, create a thread to run the task immediately;
    • If the number of running threads is greater than or equal to corePoolSize, put the task into the queue;
    • If the queue is full and the number of running threads is less than maximumPoolSize at this time, create a non core thread to run the task immediately;
    • If the queue is full and the number of running threads is greater than or equal to maximumPoolSize, the thread pool will start the saturation rejection policy to execute.
  3. When a thread completes a task, it will take a task from the queue to execute
  4. When a thread has nothing to do for more than a certain time (keepAliveTime), the thread will judge:
    1. If the number of currently running threads is greater than corePoolSize, the thread is stopped.
    2. Therefore, after all tasks of the thread pool are completed, it will eventually shrink to the size of corePoolSize.

🍦 matters needing attention

It is recommended to manually create a thread pool using ThreadPoolExecutor and its seven parameters

Why not use Executors to create?

🍕 think aloud

Recently, I started to study JUC again. I feel that there are a lot of Java contents, but in order to go further, I still feel that I should lay a solid foundation.

Recently, in the continuous update, if you feel helpful and interested, pay attention to me. Let's study and discuss together.

Hello, I'm blogger Ning Zaichun, a small seed on the Java learning road. I also hope to take root and grow into a big tree in the sky one day.

I hope to encourage you 😁

We: when we meet at parting, we have achieved everything.

reference resources:

Implementation principle of thread pool ThreadPoolExecutor

https://www.zhihu.com/question/23212914/answer/245992718 Author: Idle people

Alibaba Development Manual

Posted by AbraCadaver on Wed, 13 Oct 2021 16:54:45 -0700