Java multithreading exploration: why use ThreadPoolExecutor?

Life is short. It's better to have a dog

1, Foreword

   some time ago, I saw it when I looked through the Alibaba Java development manual again

[mandatory] thread pools are not allowed to be created by Executors, but by ThreadPoolExecutor. So why is it so stipulated?

2, Some questions about multithreaded souls

  before we begin to answer the above questions, let's answer multithreading related questions to warm up.

1. Why use multithreading?

  in fact, you can replace this problem with, what's the disadvantage of using single thread to deal with the problem? Single thread means that all threads work serially, that is, each thread must wait for the previous thread to finish processing before it can start working. When a thread needs to process a huge file, the user can only wait in front of the computer until the thread is finished.   when the concept of multithreading is introduced, all threads can work concurrently. Note that concurrent execution here refers to simultaneous execution in the same period of time, but it is still serial in micro view (when the CPU is single core). Some students will wonder why multithreading is due to single thread in the user experience since it is still serial in the micro level. This is due to the thread scheduling strategy. After introducing the concept of multithreading, each thread has a fixed time slice when using the CPU. If the execution time exceeds the specified time slice, it needs to give the CPU to other threads for use. From a macro point of view, multiple threads are working at the same time, that is, at the same time. In addition, when a thread is blocked due to IO operations, it will also give resources to other threads for use. Therefore, from the perspective of users, the introduction of multithreading improves the user's sense of experience.

Here is just some basic knowledge of thread scheduling, such as thread state flow. You can Google it by yourself. I won't repeat it here.

2. Under what scenario is multithreading used?

  after answering the above question, the new question comes: when should we use multithreading? From the answer to the previous question, we can find that when the requirements for user response are relatively high and users are allowed to access concurrently, multithreading is very suitable for use at this time. Another situation is that when the program needs time-consuming calculation or processing large files, in order to improve the response of the program, it will create sub threads to process the task.

3. Why use thread pool?

  since multithreading is so excellent, can it be used recklessly? Of course not. Any seemingly excellent thing needs to be used at a price. Just like girls' cosmetics and bags, do they look good? good-looking. Want to have it? Yes. But one problem, these things are very expensive. The same is true for the use of threads. The creation of each thread needs to occupy memory, and the scheduling of threads also needs CPU coordination and management. At the same time, the access to shared resources between threads will also lead to thread safety problems (thread safety is not discussed here for the time being), and a series of problems to be considered. These are the "costs" of using multithreading.   therefore, it is impossible to use threads without restriction and management, so how to use multithreading reasonably—— Thread pool. A fixed number of threads are created and placed in the thread pool. When a task comes, the thread is obtained from it for task execution. If all threads in the thread pool are in use, the task is placed in the blocking queue waiting for the idle thread pool. This design concept can not only ensure to enjoy the advantages of multithreading, but also prevent the harm of unlimited and unmanaged use of threads.

3, Why use ThreadPoolExecutor instead of Executors?

  after so much soul torture, we finally came to the most important question today: why use ThreadPoolExecutor?    in fact, the answer is very simple. Let's enjoy the four basic thread pools provided by Executors. After enjoying them, you should understand:

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

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

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

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
	  // ThreadPoolExecutor is used at the bottom here
    return new ScheduledThreadPoolExecutor(corePoolSize);
}

  it can be seen that the ThreadPoolExecutor is used at the bottom of these four thread pools, but the corresponding parameters Executors have been carefully set for developers. However, it is the Executors that encapsulate the specific details of the bottom layer, which makes the development unable to control the execution process of the thread pool and modify the thread pool according to the actual situation. This is a very dangerous thing for the use of multithreading. Therefore, in order to enable developers to understand the operation mechanism of thread pool in detail, it is recommended to use ThreadPoolExecutor instead of Executors to create thread pool in Alibaba Java development manual.

4, A simple example

  I won't say much. Let's use an example to appreciate how ThreadPoolExecutor works.

The codes used are as follows: TestThreadFactory

public class TestThreadFactory implements ThreadFactory {

    /**
     * Name prefix
     */
    private final String namePrefix;

    private final AtomicInteger nextId = new AtomicInteger(0);

    public TestThreadFactory(String whatFeatureOfGroup){
        this.namePrefix = "From TestThreadFactory's " + whatFeatureOfGroup + "-Worker-";
    }

    @Override
    public Thread newThread(Runnable task) {
        String name = namePrefix
                + nextId.getAndIncrement();
        Thread thread = new Thread(null, task, name, 0);
        System.out.println(thread.getName());
        return thread;
    }
}

ThreadPoolManager

public class ThreadPoolManager {

    /**
     * Core thread size
     */
    private int corePoolSize;
    /**
     * Maximum thread pool size
     */
    private int maximumPoolSize;
    /**
     * Hold time
     */
    private int keepAliveTime;

    /**
     * Time unit
     */
    private TimeUnit timeUnit;

    /**
     * Blocking queue
     */
    private BlockingQueue blockingQueue;

    /**
     * Thread factory
     */
    private ThreadFactory threadFactory;

    /**
     * Thread saturation strategy
     */
    private RejectedExecutionHandler handler;

    /**
     * Constructor
     *
     * @param corePoolSize
     * @param maximumPoolSize
     * @param keepAliveTime
     * @param timeUnit
     * @param blockingQueue
     * @param threadFactory
     * @param handler
     */
    public ThreadPoolManager(int corePoolSize, int maximumPoolSize, int keepAliveTime, TimeUnit timeUnit, BlockingQueue blockingQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler){
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.keepAliveTime = keepAliveTime;
        this.timeUnit = timeUnit;
        this.blockingQueue = blockingQueue;
        this.threadFactory = threadFactory;
        this.handler = handler;
    }


    /**
     * Create thread pool
     *
     * @return
     */
    public ThreadPoolExecutor createThreadPool(){
        return new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, blockingQueue, threadFactory, handler);
    }
}

TestMain

public class TestMain {

    public static void main(String[] args) {
        ThreadPoolManager threadPoolManager = new ThreadPoolManager(1,2,3,
                TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(3), new TestThreadFactory("test"), new ThreadPoolExecutor.AbortPolicy());
        ThreadPoolExecutor threadPoolExecutor = threadPoolManager.createThreadPool();
        for(int i = 0; i<5; i++){
            try {
                threadPoolExecutor.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " test is running");
                    try {
						   // It is used to make it easier to see the implementation
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

  you can see that there are five execution requests in the code, but only two threads are created in the thread pool to execute the request. In this process, the overhead of thread creation is greatly reduced, and the thread pool also provides the functions related to task execution scheduling management. Specific details in the next article, the free fish will be appreciated with you.

5, Summary

   after the above analysis, I believe you should have a basic understanding of why it is not recommended to directly use the thread pool method encapsulated in Executors to use thread pool. In short, I hope you don't just want convenience, because in the project, the simpler things are, the fewer scenes are applicable. Only by using the correct scheme according to the actual scene can you get the best results.

And my blog under construction: https://www.swzgeek.com

Added by webdata on Tue, 08 Feb 2022 09:57:01 +0200