Four thread pool rejection strategies

I. Introduction


Thread pool, I believe many people have used it, and those who haven't used it and believe it have also learned it. However, the rejection strategy of the thread pool, I believe people will know a lot less.


Two, four thread pool rejection strategies


When the task cache queue of the thread pool is full and the number of threads in the thread pool reaches the maximumPoolSize, if there are tasks coming, the task rejection strategy will be adopted. There are usually the following four strategies:


ThreadPoolExecutor.AbortPolicy : Discard the task and throw a RejectedExecutionException.

ThreadPoolExecutor.DiscardPolicy : Discard the task without throwing an exception.

ThreadPoolExecutor.DiscardOldestPolicy : Discard the first task in the queue, and then resubmit the rejected task

ThreadPoolExecutor.CallerRunsPolicy : The task is processed by the calling thread (the thread that submits the task). If the task is rejected, the calling thread (the thread submitting the task) directly executes the task


Third, the default rejection strategy of the thread pool


Since there are four rejection strategies to choose from, what is the default rejection strategy of the thread pool? View

The source code of java.util.concurrent.ThreadPoolExecutor class, we can see:

/**

* The default rejected execution handler

*/

private static final RejectedExecutionHandler defaultHandler =

new AbortPolicy();

The default rejection policy of the thread pool is AbortPolicy, that is, discarding tasks and throwing RejectedExecutionException. We can verify this by code, the existing code is as follows:

public class ThreadPoolTest {
        public static void main(String[] args) {
            BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(100);
            ThreadFactory factory = r -> new Thread(r, "test-thread-pool");
            ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
                    0L, TimeUnit.SECONDS, queue, factory);
            while (true) {
                executor.submit(() -> {
                    try {
                        System.out.println(queue.size());
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }

Here is a default thread pool, no rejection policy is set, and the maximum thread queue is set to 100. Run the code:


The result is in line with expectations, which also proves that the default rejection policy of the thread pool is ThreadPoolExecutor.AbortPolicy: discard tasks and throw RejectedExecutionException.


Fourth, set the thread pool rejection strategy


If we want to set other thread pool rejection strategies according to actual business scenarios, we can set them through the ThreadPoolExecutor overloaded construction method:


In the current development, I believe everyone uses spring. In fact, we can also build a thread pool through org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor provided by spring. as follows:

@Configuration
    public class TaskExecutorConfig implements AsyncConfigurer 

    {
/**
 * Set the ThreadPoolExecutor's core pool size.
 */
        private static final int CORE_POOL_SIZE = 5;
/**
 * Set the ThreadPoolExecutor's maximum pool size.
 */
        private static final int MAX_POOL_SIZE = 5;
/**
 * Set the capacity for the ThreadPoolExecutor's BlockingQueue.
 */
        private static final int QUEUE_CAPACITY = 1000;
/**
 * 通过重写getAsyncExecutor方法,制定默认的任务执行由该方法产生
 * <p>
 * 配置类实现AsyncConfigurer接口并重写getAsyncExcutor方法,并返回一个ThreadPoolTaskExevutor
 * 这样我们就获得了一个基于线程池的TaskExecutor
 */
        @Override
        public Executor getAsyncExecutor () {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setQueueCapacity(QUEUE_CAPACITY);
        taskExecutor.initialize();
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        return taskExecutor;
    }
    }


Five, rejection strategy scenario analysis


(1)AbortPolicy
AbortPolicy

ThreadPoolExecutor.AbortPolicy: Discard the task and throw a RejectedExecutionException.

A handler for rejected tasks that throws a {@code RejectedExecutionException}.

This is the default rejection strategy of the thread pool. When the task can no longer be submitted, an exception is thrown and the program running status is reported back in time. If it is a more critical business, it is recommended to use this rejection strategy, so that when the system cannot carry a larger amount of concurrency, it can be found through exceptions in time.

(2)DiscardPolicy

ThreadPoolExecutor.DiscardPolicy: Discard the task without throwing an exception. If the thread queue is full, all subsequent submitted tasks will be discarded and discarded silently.

A handler for rejected tasks that silently discards therejected task.

Using this strategy may prevent us from discovering the abnormal state of the system. The suggestion is that some unimportant businesses adopt this strategy. For example, my blog website statistics reading volume is the rejection strategy adopted.

(3)DiscardOldestPolicy

ThreadPoolExecutor.DiscardOldestPolicy: Discard the first task in the queue, and then resubmit the rejected task.

A handler for rejected tasks that discards the oldest unhandled request and then retries {@code execute}, unless the executor is shut down, in which case the task is discarded.

This rejection strategy is a rejection strategy that likes the new and dislikes the old. Whether to adopt this kind of rejection strategy must be carefully measured according to whether the actual business allows the discarding of old tasks.

(4)CallerRunsPolicy

ThreadPoolExecutor.CallerRunsPolicy: the task is handled by the calling thread

 
  1. A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.

  2.  

If the task is rejected, the calling thread (the thread submitting the task) directly executes the task. We can verify this by code:

Modify the previous code as follows:

public static void main(String[] args) {

        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(10);

        ThreadFactory factory = r -> new Thread(r, "test-thread-pool");

        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,

                0L, TimeUnit.SECONDS, queue, factory, new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 1000; i++) {

            executor.submit(() -> {

                try {

                    System.out.println(Thread.currentThread().getName() + ":执行任务");

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            });

        }

    }

 

 

Change the maximum value of the queue to 10 and print out the name of the thread. The results are as follows:

 

 

It can be seen from the results that the main thread main also performed the task, which just shows that this rejection strategy directly executes the discarded task by the calling thread (the thread submitting the task).

Six, summary


This article introduces and demonstrates four thread pool rejection strategies. Which strategy to use depends on actual business scenarios to make a decision.

Guess you like

Origin blog.csdn.net/My_Way666/article/details/112601799
Recommended