Alibaba recommended thread pool creation method

Keywords: Java thread pool

Executor is not recommended
The Executors class provides us with various types of thread pools. The frequently used factory methods are:

public static ExecutorService newSingleThreadExecutor()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newCachedThreadPool()
public static ScheduledExecutorService newSingleThreadScheduledExecutor()
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

Test demo

public class ThreadDemo {
 public static void main(String[] args) {
  ExecutorService es = Executors.newSingleThreadExecutor();
 }
}

When we check the code with Alibaba's P3C, we will be educated!!!!

Ali's father is not allowed to create a thread pool in this way. The warning above is very clear: "thread pools are not allowed to be created using Executors, but through ThreadPoolExecutor. This processing method makes the students who write more clear about the operation rules of thread pools and avoid the risk of resource depletion." (PS: it's very rare to see Chinese prompts in the compiler. It's a blessing for students with poor English. They cry with joy!!!)

Force use of ThreadPoolExecutor
We use ThreadPoolExecutor to create a thread pool:

public class ThreadDemo {
 public static void main(String[] args) {
  ExecutorService es = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<Runnable>(10), Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.DiscardPolicy());
 }
}

At this time, use P3C to check the code, and finally no error is reported.

After the gorgeous separator, we still need to dig into the principle from the level of JDK source code.

The first is the static methods newsinglethreadexecution(), newFixedThreadPool(int nThreads) and newCachedThreadPool(). Let's take a look at their source code implementation (based on JDK8).

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

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

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

By looking at the source code, we know that the internal implementation of the above three static methods uses the ThreadPoolExecutor class. No wonder Ali's father would suggest that it be implemented through ThreadPoolExecutor. It was also used for the static method of Executors class, but it just helped us configure some parameters.

The second is the construction method of ThreadPoolExecutor class. Now that we want to directly use ThreadPoolExecutor class, we need to configure the initialization parameters ourselves. It is imperative to understand its construction method.

ThreadPoolExecutor class has four constructors. We only need to know one of them, because the other three constructors only configure some default parameters for us, and finally call it.

public ThreadPoolExecutor(int corePoolSize,
            int maximumPoolSize,
            long keepAliveTime,
            TimeUnit unit,
            BlockingQueue<Runnable> workQueue,
            ThreadFactory threadFactory,
            RejectedExecutionHandler handler)

The meaning of the parameter is:

corePoolSize: the number of threads in the thread pool;
maximumPoolSize: the maximum number of threads in the thread pool;
keepAliveTime: when the number of threads in the thread pool exceeds the corePoolSize, how long will the redundant idle threads be destroyed;
Unit: the time unit of keepAliveTime;
workQueue: task queue, a task submitted but not yet executed;
threadFactory: thread factory, which is used to create threads. Generally, the default is used, that is, the static method defaultThreadFactory(); handler: reject policy. How to reject tasks when there are too many tasks to process.
For these parameters, you should have the following understanding:

Relationship between corePoolSize and maximumPoolSize

First, the corePoolSize must be < = maximumpoolsize.

Other relationships are as follows:

If the number of threads in the current thread pool is < corepoolsize, a thread will be created for each task to execute;
If the number of threads in the current thread pool > = corepoolsize, it will try to add the task to the task queue. If the addition is successful, the task will wait for the idle thread to take it out and execute;
If the queue is full and the number of threads in the current thread pool is < maximumpoolsize, create a new thread;
If the number of threads in the current thread pool > = maximumpoolsize, the rejection policy will be adopted (JDK provides four, which will be introduced below).
Note: relationship 3 is for bounded queues. Unbounded queues will never be full, so there are only the first two relationships.

workQueue

The parameter workQueue refers to the task queue submitted but not executed. If the number of threads in the current thread pool > = corepoolsize, the task will be added to the task queue. There are mainly the following types:

SynchronousQueue: direct submission queue. SynchronousQueue has no capacity, so actually submitted tasks will not be added to the task queue. New tasks will always be submitted to threads for execution. If there are no idle threads, try to create new threads. If the number of threads has reached the maximum poolsize, execute the rejection policy.
LinkedBlockingQueue: unbounded task queue. When a new task arrives, if the number of threads in the system is less than the corePoolSize, the thread pool will create a new thread to execute the task; when the number of threads in the system is equal to the corePoolSize, because it is an unbounded task queue, the task can always be successfully added to the task queue, so the number of threads will not increase. If the task creation speed is slow The degree is much faster than the speed of task processing, and the unbounded queue will grow rapidly until the memory is exhausted.
handler

JDK has four built-in rejection policies:

DiscardOldestPolicy policy: discard the earliest added task in the task queue and try to submit the current task;
CallerRunsPolicy policy: call the main thread to execute the rejected task, which provides a simple feedback control mechanism, which will reduce the submission speed of new tasks.
Discard policy: silently discard tasks that cannot be processed without any processing.
AbortPolicy policy: throw exceptions directly to prevent the system from working normally.
At this point, we can directly new ThreadPoolExecutor class, so don't panic!!!!

Posted by murtoz on Sun, 24 Oct 2021 18:06:51 -0700