preface

I haven’t updated my article for a long time. I’m not lazy, but I started another project (Spring). Then your mind gets blown up in the process of studying, so write something else to ease the mood. When Spring is finished, the corresponding article will be published.

Thread pool background

In an application, we must use threads multiple times, which means we must create and destroy threads multiple times. The process of creating and destroying threads inevitably consumes memory. In Java, memory resources are extremely valuable, so we came up with the concept of thread pools.

Of course, there are a number of benefits to using thread pools:

  • Reduced resource consumption: Reuse of created threads through pooling techniques to reduce wastage from thread creation and destruction.
  • Improved response time: Tasks can be executed immediately when they arrive without waiting for threads to be created.
  • Improve manageability of threads: Threads are scarce resources. If they are created without limit, they will not only consume system resources, but also cause resource scheduling imbalance due to unreasonable distribution of threads, which reduces system stability. Thread pools allow for uniform allocation, tuning, and monitoring.

Parameter structure of the thread pool

To use thread pools well, we must understand their underlying design. So today, let me comb through it with you.

The thread pool family is made up of nested ThreadPoolExecutor methods. So understanding ThreadPoolExecutor is the key to understanding thread pools.

    public ThreadPoolExecutor(intCorePoolSize, // limit the number of core threadsintMaximumPoolSize,// Maximum thread limitlongKeepAliveTime,// Idle thread lifetime TimeUnit unit, BlockingQueue<Runnable> workQueue,// ThreadFactory ThreadFactory,// ThreadFactory RejectedExecutionHandler handler) {// Reject the policy
Copy the code

Make sure you write down these 7 parameters. Here’s how a thread pool works:

Combined with this figure, let’s further understand the meaning of each parameter.

The number of threads

Threads are divided into core threads and non-core threads. Number of non-core threads = maximum number of threads – number of core threads. A brief overview of the thread pool workflow:

  1. If there is a task, it should be assigned to the core thread first.
  2. If the core thread is not idle and has work to do, then it is stored in the blocking queue.
  3. If the blocking queue is full, it is redistributed to the least core thread.
  4. If the non-core thread is not idle, the reject policy is adopted.

Idle thread lifetime

KeepAliveTime: Indicates the keepAliveTime for idle threads. When the number of thread pools exceeds corePoolSize, the keepAliveTime value is reached

Task queue BlockingQueue

BlockingQueue cannot add null objects, otherwise a null pointer exception will be thrown.

Common task queues are

  • LinkedBlockingQueue

A blocking queue based on a linked list structure, sorting tasks according to FIFO. The capacity can be optionally set. If not set, the queue will be a borderless blocking queue with a maximum length of integer.max_value.

  • ArrayBlockingQueue

Is a bounded blocking queue with an array, sorted by FIFO.

  • SynchronousQueue

Synchronous queue, a blocking queue that does not store elements, each insert operation must wait until another thread calls the remove operation, otherwise the insert operation remains blocked

  • PriorityBlockingQueue

Is a sorted BlockingQueue implementation with an unbounded queue.

Remember what I’ve highlighted in bold, it’ll be useful later.

Thread factory

When using the thread pool, it is highly recommended to use your own thread factory, so that you can name the threads in the thread pool, so that you can quickly identify the corresponding threads when using the jsatck command to view the thread stack.

Rejection policies

There are four abortpolicies: CallerRunsPolicy, DiscardOldestPolicy, and DiscardPolicy. So let’s introduce the four rejection strategies:

  • AbortPolicy: Default policy. If the status of the current thread pool is not RUNNING, the queue is full, and the number of current threads reaches the maximum, the task will throw an exception.
  • CallerRunsPolicy: This rejection policy executes the discarded task directly from the calling thread (the thread submitting the task).
  • DiscardOldestPolicy: Discards the task at the top of the queue and resubmits the rejected task.
  • DiscardPolicy: Discards the task without throwing an exception. If the thread queue is full, all subsequent submitted tasks are discarded silently.

How do I properly configure thread pool parameters

Now baidu will have two calculation formulas respectively about IO intensive and CPU intensive, but I read a lot of big blogs found that they do not agree with this view, so the final conclusion is: with my 4 core machine:

Number of threads = number of CPU cores /(1- blocking factor)

The blocking coefficient is usually (0.8 ~ 0.9). The blocking factor is 0.8 and the number of threads is 20. The blocking factor is 0.9, which is about 40 threads, 20 threads, I think. The following figure shows the thread pool parameters used for meituan’s in-store development.

It is worth noting that the so-called CPU intensive refers to pure computing, such as calculating the number of digits of PI. Of course, we are basically out of touch, and the business we touch still has to be associated with databases and middleware through requests. If you have to say CPU intensive, then CPU intensive should not be set to multi-threading, because it will cause thread switching, but loss of performance. A little bit smaller than the formula above. (I did not find the appropriate information here, after all, parameter setting still needs to be determined according to the specific business)

By the way, how to get the current server core thread count:

System.out.println(Runtime.getRuntime().availableProcessors());
Copy the code

My computer result is 4 cores:

Simple introduction

As we all know, there are five types of thread pools, two of which are timed and not the target of this discussion. This time we will focus on the other three, newFixedThreadPool, newSingleThreadExecutor, and newCachedThreadPool. Let’s start with a simple example.

public class ThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService es1 = Executors.newCachedThreadPool();
        ExecutorService es2 = Executors.newFixedThreadPool(10);
        ExecutorService es3 = Executors.newSingleThreadExecutor();
        es1.execute(new MyTask(i));
        //es2.execute(new MyTask(i));
        //es3.execute(new MyTask(i));}}class MyTask implements Runnable {
    int i = 0;
    public MyTask(int i) {
        this.i = i;
    }
    @Override
    public void run(a) {
        System.out.println(Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

For a complete example, let’s take a look at each of the three thread pools.

newCachedThreadPool

public static ExecutorService newCachedThreadPool(a) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
    }
Copy the code

MAX_VALUE (2^ 31-1 = 2,147,483,647, 2,147,210,000) The lifetime of the thread is 60 seconds and the task queue is BlockingQueue.

As you can see, all threads in the thread pool are created in the non-core thread area (since the core thread area is 0), so if I have 100 jobs and I can find 100 workers to do it for me, I can do it very quickly, but we know that the number of threads is dependent on the CPU. Since there is no limit to how many threads can be created, all threads are created in the non-core thread area, and it takes a lot of CPU performance to create threads, so newCachedThreadPool is likely to cause 100% CPU performance.

newFixedThreadPool

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

The number of core threads and the maximum number of threads are both passed in. The keepAliveTime of idle threads is 0s (keepAliveTime is the keepAliveTime of non-core threads. Note that the task queue is LinkedBlockingQueue. LinkedBlockingQueue internally stores elements based on a linked list. If the size is not specified, it defaults to integer. MAX_VALUE, which is an unbounded queue. (Normally, the size is set, but this is not set. It is the default.) . There is a problem with newFixedThreadPool, however, because the initial size of LinkedBlockingQueue is not set, it will be infinitely large and will most likely result in an OOM.

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor(a) {
    return new FinalizableDelegatedExecutorService
      (new ThreadPoolExecutor(1.1.0L, TimeUnit.MILLISECONDS,
                              new LinkedBlockingQueue<Runnable>()));
}
Copy the code

The same as newFixedThreadPool except that the number of core threads and the maximum number of threads are 1. That is, it is a task by task, so newSingleThreadExecutor is the slowest of the three. Of course, newSingleThreadExecutory still has OOM problems.

The Ali development manual enforces the use of custom thread pools.

Status of the thread pool

After talking about parameters and usage, let’s talk about thread pool state. Thread pools are in five states: RUNNING, SHOUTDOWN, STOP, TIDYING, and TERMINATED.

Haggis and shutdownNow are a few things to talk about. The haggis method, which works on a TIDYING state in the work queue and on a thread pool when there are other tasks in the queue, is very friendly. ShutdownNow, on the other hand, only completes tasks in the current thread pool, rather than terminating tasks in the work queue, which is more violent.

conclusion

In this article, I have a brief understanding of the principle of thread pool, in which the configuration parameters of thread pool is particularly important, how to reasonably configure also need to pay special attention to. It also briefly shows the underlying logic for creating a thread pool, as well as the thread pool native API, and explains why custom thread pools are not recommended. The following is a simple thread pool workflow. The source code will be presented in the next post, coming up soon.

Standing on the shoulders of giants

  • 【 Java thread pool implementation principle and its practice in the business of Meituan 】 tech.meituan.com/2020/04/02/…

  • B station source lecturer liu @ grumpy liu up main homepage: space.bilibili.com/457326371?f…

  • See how breadman’s ‘signature thread pool’ works? Mp.weixin.qq.com/s/tvEwOpVH-…

  • Interviewer: how do you evaluate what you need to set up a thread pool line 】 mp.weixin.qq.com/s/3E6qdOci-…