Scan the qr code below or search the wechat official account, cainiao Feiyafei, you can follow the wechat official account, read more Spring source code analysis and Java concurrent programming articles.

preface

In chapter 1 of “Alibaba Java Development Manual”, chapter 6 of Concurrent processing, it is mandatory that thread pool is not allowed to create by Executors. So why? Back to the nature of thread pools and Executors.

The thread pool ThreadPoolExecutor

In Java provides two types of thread pool for use by the developer, is ThreadPoolExecutor and ScheduledThreadPoolExecutor, respectively. Including ScheduledThreadPoolExecutor inherited ThreadPoolExecutor, type of UML diagram as shown below. ScheduledThreadPoolExecutor function and is similar to the Timer in Java, it provides the timing to perform a task or fixed time delay to execute the tasks of function, its function is more powerful than the Timer. (For a detailed source code analysis of thread pool principles, see this article: ThreadPoolExecutor implementation principles.)

parameter function
int corePoolSize The number of core threads in the thread pool
int maximumPoolSize The maximum number of threads in the thread pool
long keepAliveTime The maximum idle time of an idle thread
TimeUnit unit Units of free time,TimeUnitIs an enumerated value, which can be nanosecond, subtle, millisecond, second, minute, hour, day
BlockingQueue workQueue Stores the blocking queues of tasks. Common blocking queues include ArrayBolckingQueue, LinkedBlockingQueue, SynchronousQueue, and PriorityQueue
ThreadFactory threadFactory A factory for creating threads, which are typically assigned business-meaningful names when threads are created using thread factories
RejectedExecutionHandler handler Rejection strategy. When the number of threads in the thread pool exceeds the maximum number, the thread pool cannot accept any more tasks. In this case, a reject policy is required

Why are these seven parameters important? The thread pool ThreadPoolExecutor is based on these parameters. When the main thread submits a task to the thread pool, the thread pool execution flow is as follows:

  • 1.Check whether the number of threads in the thread pool is smaller than the number of core threadscorePoolSizeIf it is smaller than corePoolSize, a new thread is created to execute the task. Otherwise enter the following process.
  • 2.Check whether the task queue is full, that is, checkworkQueueIs it full? If not, add the task to the task queue; If it is, proceed to the following process.
  • 3.Then check whether the number of threads is greater than the maximum number of threads after a new thread is createdmaximumPoolSize, if greater than maximumPoolSize, enter the following process; Otherwise, a new thread is created to perform the task.
  • 4.To execute a denial policy, executehandlertherejectedExecution()methods

Executors

The constructor of the ThreadPoolExecutor class is cumbersome to create because it takes too many parameters, and ThreadPoolExecutor can be subdivided into three types of thread pools, making it difficult to create. * * * provides static methods to create thread pools by calling those static methods and passing in few or no parameters. * / * Creates thread pools by following the Executors class. Executors is a tool class for creating thread pools. As mentioned above, ThreadPoolExecutor has seven very important parameters. When we pass special values to these parameters, the created ThreadPoolExecutor thread pool can be divided into three categories: FixedThreadPool (FixedThreadPool), SingleThreadExecutor (single thread pool), CachedThreadPool (unbounded thread pool). Note that FixedThreadPool, SingleThreadExecutor, and CachedThreadPool are not actual class names, but aliases based on the particularness of the thread pool. Let’s look at these three thread pools in detail.

FixedThreadPool

A FixedThreadPool is a thread pool with a fixed number of threads, that is, the number of core threads equals the maximum number of threads. The Executors factory class provides the following two methods to create FixedThreadPool.

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

// Specify the number of threads and the thread factory
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
Copy the code

* If the thread count/Max threads = nThreads / * if the thread count reaches the core threads / * if the thread count/Max threads = nThreads / * if the thread count reaches the core threads / * then the thread count does not change. The number of core threads is always maintained, so the pool created by this method is called a thread pool with a fixed number of threads. The keepAliveTime parameter is set to 0 because there are no idle threads in the thread pool when coolPoolSize equals maximumPoolSize. The keepAliveTime parameter is the maximum duration for which idle threads can live. There is no chance of idle threads, and it makes no sense to set keepAliveTime to a value greater than 0, so here it is set to 0. The task queue uses LinkedBlockingQueue, and since LinkedBlockingQueue defaults to integer.max_value when initialized without specifying the size, it is often called an unbounded queue. The following problems occur when using unbounded queues:

  • 1.When the number of threads reaches the number of core threads, the newly added task will be put into the task queue. Since the unbounded queue is used, the task can be added to the queue indefinitely, which may result in OOM. The maximunPoolSize parameter is invalid because the task queue can always store tasks.
  • 2.If the number of threads does not exceed maximunPoolSize, there will be no idle threads, which invalidates keepAliveTime.
  • 3.With unbounded queues, so that the number of threads does not exceed maximunPoolSize, the rejection policy is never executed, i.e. the handler parameter is invalidated. For applications with high server load and strict resource control, threads cannot be created at will. In this case, FixedThreadPool is suitable because the number of threads is fixed. If the number of threads is determined in advance, service exceptions will not occur due to improper configuration of the thread pool.

SingleThreadExecutor

SingleThreadExecutor, a thread pool with 1 thread, i.e., the number of core threads and the maximum number of threads are 1. The Executors factory class provides the following two methods to create SingleThreadExecutor.

// Without passing any arguments, the ThreadPoolExecutor constructor simply sets the core thread count and the maximum thread count to 1
public static ExecutorService newSingleThreadExecutor(a) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1.1.0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

In the ThreadPoolExecutor constructor, set the number of core threads and the maximum number of threads to 1
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1.1.0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}
Copy the code

In the ThreadPoolExecutor constructor, maximunPoolSize and corePoolSize are set to 1 so that there will always be only one thread in the pool, a single thread pool. Also, because there are no idle threads, keepAliveTime is set to 0. The task queue still uses LinekdBlockingQueue, or unbounded queue. An OOM exception may occur and keepAliveTime, maximunPoolSize, and handler parameters fail due to the use of unbounded queues. For scenarios where tasks need to be executed sequentially, use SingleThreadExecutor.

CachedThreadPool

CachedThreadPool: a thread pool with an unlimited number of threads. The core thread count is zero, and the maximum thread count is integer.max_value, which is already so large that it is called an unbounded thread pool. The ExechedThreadPool class provides the following two methods to create a CachedThreadPool.

// Without passing any arguments, in the constructor of ThreadPoolExecutor, set the number of core threads to 0 and the maximum number of threads to integer.max_value
public static ExecutorService newCachedThreadPool(a) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

// Specify a thread-creation factory, and then in the constructor of ThreadPoolExecutor, set the number of core threads to 0 and the maximum number of threads to integer.max_value
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>(),
                                  threadFactory);
}
Copy the code

As you can see from the above code, the ThreadPoolExecutor constructor sets the number of core threads to 0 and the maximum number of threads to integer. MAX_VALUE. Since integer. MAX_VALUE is very large, Therefore, a CachedThreadPool is often called an unbounded thread pool. Set keepAliveTime to 60 seconds, which means that an idle thread can live for 60 seconds at most. In CachedPoolPool, the blocking queue used is no longer LinkedBlockingQueue, but SynchronousQueue, which is a blocking queue that does not store elements. When a thread puts an element into a queue, it must wait for another thread to fetch the element from the queue before it returns. If no thread pulls an element from the queue, the current thread blocks until the element is fetched. That’s why SynchronousQueue is a queue that stores no elements. Since the number of core threads is 0, the level 1 judgment fails when a task is submitted to the thread pool. The offer() method of the blocking queue is called to try to add the task to the task queue. Since the blocking queue is SynchronousQueue and does not store elements, the offer() method returns false, indicating that the second judgment is not true (the task cannot be added to the queue). If the current number of threads is greater than the maximum number of threads, which is obviously not the case, because the maximum number of threads is integer.max_value, a new thread will be created to handle the task. As soon as a new task is added to the pool, a new thread is created to handle the task, hence the CachedThreadPool is an unbounded thread pool. Threads in the pool are idle for a maximum of 60 seconds, and when no task is retrieved from the blocking queue for 60 seconds, the thread is destroyed. When the main thread submits tasks faster than the thread pool can process them, the thread pool will keep creating threads, which may result in an OOM exception. When a large number of tasks are executed for a short time, a thread pool such as CacheThreadPool is suitable for processing tasks.

Under JUC package also provides a common thread pool, it is ScheduledThreadPoolExecutor. ScheduledThreadPoolExecutor is a subclass of ThreadPoolExecutor, its function is to perform tasks on a regular basis or perform a task after the given delay. Will be the core of the thread pool parameter is set to the value of the special, will create two types of ScheduledThreadPoolExecutor. Contain multiple threads ScheduledThreadPoolExecutor and SingleScheduledThreadExecutor contains only a single thread. (note: SingleScheduledThreadExecutor is not a class name, but according to the characteristics of the thread pool to pick up a name). Also, Executors static factory class provides a relevant ScheduledThreadPoolExecutor create static methods. Under the combined code examples to analysis two types of ScheduledThreadPoolExecutor, respectively.

Multiple threads ScheduledThreadPoolExecutor

When the core number of threads specified for multiple ScheduledThreadPoolExecutor (greater than 1), ScheduledThreadPoolExecutor is multithreaded thread pool. Executors factory class provides the following two methods to create multiple threads ScheduledThreadPoolExecutor.

// Specifies the number of core threads
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

// Specifies the number of core threads and the thread factory
public static ScheduledExecutorService newScheduledThreadPool(
        int corePoolSize, ThreadFactory threadFactory) {
    return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
Copy the code

When the incoming parameters corePoolSize is greater than 1, is a multithreaded ScheduledThreadPoolExecutor, when the incoming value is equal to 1, becomes the single thread SingleThreadScheduledExecutor. See below ScheduledThreadPoolExecutor construction method with a parameter. The source code is as follows:

public ScheduledThreadPoolExecutor(int corePoolSize) {
	// The number of core threads is the number of incoming threads, i.e., 1
	// The maximum number of threads is integer.max_value
	The blocking queue used is DelayedWorkQueue, which is an unbounded queue
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}
Copy the code

Can be found that the maximum number of threads ScheduledThreadPoolExecutor Integer. MAX_VALUE, use DelayedWorkQueue queue, this is an unbounded queue. Since it is an unbounded queue, the maximum number of threads maximumPoolSize is invalid, so it is useless to set the maximum number of threads to integer.max_value. What is the DelayedWorkQueue? It is ScheduledThreadPoolExecutor definition of a static inner class, it is the nature of a delay queue, its function and DelayQueue similar. In DelayQueue, contains a property of type PriorityQueue. DelayedWorkQueue is a combination of DelayQueue and PriorityQueue. It wraps the tasks submitted to the thread pool into a RunnableScheduledFuture object, which is then sorted according to certain rules. RunnableScheduledFuture is a private inner class ScheduledThreadPoolExecutor, inherited FutureTask. It contains three very important properties:

  • 1.SequenceNumber, the sequenceNumber when the task is added to the thread pool
  • 2.Time: specifies the time at which the task is executed
  • 3.Period: period of task execution

DelayedWorkQueue will sort all runnablesCheduledFutures in the queue from smallest to largest by the time of each RunnableScheduledFuture. The RunnableScheduledFuture with the smallest time should be executed first. When multiple tasks occur at the same time, they are sorted from smallest to largest by sequenceNumber so that they can be executed regularly in the thread pool.

ScheduledThreadPoolExecutor mission detailed steps are as follows:

  • 1.Peek () is used to obtain the first task from the DelayedWorkQueue and check whether the execution time of the task is less than the current time. If not, the execution time of the task has not reached the current time and let the thread continue to wait for some time. If less than or equal to, the following process is performed.
  • 2.The poll() operation retrieves the first task from the queue and, if there are still tasks in the queue, wakes up the threads in the waiting queue and notifies them to try to get the task as well.
  • 3.The current thread executes the fetched task.
  • 4.After executing the task, change the value of the Time property of the RunnableScheduledFuture task to the point in time when it will be executed next, and then put the task back on the task queue.

SingleScheduledThreadExecutor

SingleThreadScheduledExecutor refers to only one thread pool threads ScheduledThreadPoolExecutor, core number of threads to 1 at this time. Executors factory class provides the following two methods to create SingleScheduledThreadExecutor.

// Set the number of core threads to 1 without passing any arguments
public static ScheduledExecutorService newSingleThreadScheduledExecutor(a) {
    / / will be packed ScheduledThreadPoolExecutor became DelegatedScheduledExecutorService
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1));
}

// Pass in the thread factory and specify a core thread count of 1
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
    return new DelegatedScheduledExecutorService
        (new ScheduledThreadPoolExecutor(1, threadFactory));
}
Copy the code

Logic and multithreading ScheduledThreadPoolExecutor SingleScheduledThreadExecutor mission. The only difference is that it has only one thread to execute tasks, so it can ensure the order of execution of tasks, suitable for scenarios that need to ensure that tasks are executed in order.

conclusion

  • This article details several types of thread pools. The common thread pool, ThreadPoolExecutor, can be broken down into three types of thread pools, depending on the core parameters set: FixedThreadPool (FixedThreadPool), single thread pool (SingleThreadExecutor), unbounded thread pool (CachedThreadPool); For timing task types of thread pool ScheduledThreadPoolExecutor can also according to the different Settings of the core parameters, can be subdivided into two categories: Multithreaded ScheduledThreadPoolExecutor SingleThreadScheduledExecutor and single thread, the only difference is the number of threads in thread pool.
  • It also introduces the use of static factory Executors and how to use it to create several thread pools mentioned in the article.
  • Finally, back to the beginning of this article, why is the Alibaba Java Development Manual banning the use of Executors to create thread pools? * If you can create a thread pool by following the Executors static factory class, why disable it? * If you can create a thread pool by following the Executors static factory class, why not? The thread pool created by Executors uses unbounded queues. The most important thing is that the thread pool can save tasks indefinitely, so it may cause OOM exceptions. In some types of thread pools, using unbounded queues also invalidates parameters such as maxinumPoolSize, keepAliveTime, and handler. Therefore, current development specifications for Dachang emphasize the prohibition of using Executors to create thread pools.
  • The answer to this question is simple. The key is to understand the seven important core parameters of a thread pool and understand how a thread pool works and what each parameter means. Understand their principle, whether for the interview, or in the usual work, as well as troubleshooting problems are of great help.

recommended

  • How ThreadPoolExecutor is implemented
  • The implementation principle of ReadWriteLock
  • Design principle of queue synchronizer (AQS)
  • Semaphore source code analysis and use scenarios
  • Concurrency tool class CountDownLatch source analysis and usage scenarios
  • CyclicBarrier concurrency tool class source analysis and usage scenarios
  • @ Import and @ EnableXXX
  • How the @autowired annotation works