03 rejection policy of thread pool

Posted by Wabin on Sat, 01 Jan 2022 09:23:03 +0100

1 why customize thread pool

  • FixedThreadPool and SingleThreadPool

The allowed request queue length is integer MAX_ Value, which will accumulate a large number of requests for OOM

  • CachedThreadPool and ScheduledThreadPool

The number of threads allowed to create is integer MAX_ Value, a large number of threads may be created

2 how to customize thread pool

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

3 custom thread pool rules

  • The number of threads in the thread pool is maintained at corePoolSize (number of core threads) for a long time
  • The maximum number of threads in the thread pool can be extended to maximumPoolSize
  • Threads in the range from corepoolsize to maximumpoolsize will be killed (time unit) if they are idle for more than keepAliveTime
  • When the number of threads sent to work exceeds the maximum number, they are sent to the workQueue for employment
  • When the waiting list is full, reject the RejectedExecutionHandler according to the agreed strategy

4. Reject policy of thread pool

All rejection policies implement the interface RejectedExecutionHandler

public interface RejectedExecutionHandler {
    /**
     * @param r  Tasks to be performed
     * @param executor Thread pool
     * @throws RejectedExecutionException  Method may throw a reject exception
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

4-1 AbortPolicy

Throwing a reject exception directly (inherited from RuntimeException) will interrupt the caller's processing, so it is generally not recommended unless there is a clear requirement

    public static class AbortPolicy implements RejectedExecutionHandler {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

4-2 CallerRunsPolicy

Run the currently discarded task in the caller thread (that is, who dumped r the task).

Only the thread of the caller will be used to run the task, that is, the task will not enter the thread pool.

If the thread pool has been closed, the task is discarded directly.

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
                r.run();
        }
    }
}

4-3 DiscardOledestPolicy

Discard the oldest in the queue and try to submit a new task again.

    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                //Get the task queue to be executed, queue first in first out
                //The poll() method can directly discard the oldest in the queue and try to execute(r) again
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

The BlockingQueue queue can be seen when the process pool is defined. It is a blocking queue

4-4 DiscardPolicy

Silently discard tasks that cannot be loaded.

This code is very simple. I really didn't do anything

    public static class DiscardPolicy implements RejectedExecutionHandler {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

4-5 implement RejectedExecutionHandler interface extension

The code of the four built-in rejection policies in jdk (all in ThreadPoolExecutor.java) is very concise and easy to understand.

As long as we inherit the interface, we can customize the rejection policy according to our own needs. Here are two examples.

One is a private rejection policy in the thread pool implemented by netty. Start a new temporary thread separately to execute the task.

    private static final class NewThreadRunsPolicy implements RejectedExecutionHandler {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            try {
                final Thread t = new Thread(r, "Temporary task executor");
                t.start();
            } catch (Throwable e) {
                throw new RejectedExecutionException(
                        "Failed to start a new thread", e);
            }
        }
    }

Another example is dubbo, which directly inherits AbortPolicy, enhances log output, and outputs dump files

public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        String msg = String.format("Thread pool is EXHAUSTED!" +
                        " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d)," +
                        " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
                threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(),
                e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
                url.getProtocol(), url.getIp(), url.getPort());
        logger.warn(msg);
        dumpJStack();
        throw new RejectedExecutionException(msg);
    }
}